瀏覽代碼

Resolve merge conflict

Neal Wilson 7 年之前
父節點
當前提交
5baf5443b0

+ 3
- 0
config/ImportFilters/Example.js 查看文件

@@ -0,0 +1,3 @@
1
+print(pluginContext.text);
2
+print("A plugin has run\n");
3
+pluginContext.cq.trigger();

+ 21
- 0
config/ImportFilters/Ikawa.js 查看文件

@@ -0,0 +1,21 @@
1
+pluginContext.table.setHeaderData(0, "Time");
2
+pluginContext.table.setHeaderData(1, "Temperature");
3
+pluginContext.table.setHeaderData(2, "Set");
4
+pluginContext.table.setHeaderData(3, "Fan");
5
+pluginContext.table.setHeaderData(4, "Heater");
6
+pluginContext.table.setHeaderData(5, "Note");
7
+var lines = pluginContext.data.split('\n');
8
+for(var i = 0; i < lines.length; i++) {
9
+	var fields = lines[i].split(',');
10
+	if(fields[5] == "roasting") {
11
+		var time = new QTime;
12
+		time = time.addSecs(Number(fields[0]));
13
+		pluginContext.newMeasurement(new Measurement(Units.convertTemperature(fields[4], Units.Celsius, Units.Fahrenheit), time), 1);
14
+		pluginContext.newMeasurement(new Measurement(Units.convertTemperature(fields[2], Units.Celsius, Units.Fahrenheit), time), 2);
15
+		pluginContext.newMeasurement(new Measurement(fields[1], time, Units.Unitless), 3);
16
+		pluginContext.newMeasurement(new Measurement(fields[6], time, Units.Unitless), 4);
17
+	}
18
+}
19
+for(var i = 1; i < 5; i++) {
20
+	pluginContext.table.newAnnotation("End", i, 5);
21
+}

+ 3
- 1
config/Reports/itemtransactions.xml 查看文件

@@ -222,7 +222,7 @@
222 222
                     }, 0));
223 223
                     output.writeEndElement();
224 224
                     output.writeEndElement();
225
-                    query.prepare("SELECT time::date, type, quantity / :c1, balance / :c2, (SELECT files FROM roasting_log WHERE roasting_log.time = item_history.time AND item = ANY(unroasted_id)), (SELECT invoice_id FROM invoice_items WHERE item = item_id AND item_history.type = 'PURCHASE'), (SELECT vendor || ' ' || invoice FROM invoices WHERE id = (SELECT invoice_id FROM invoice_items WHERE item = item_id AND item_history.type = 'PURCHASE')), (SELECT name FROM items WHERE id = (SELECT roasted_id FROM roasting_log WHERE roasting_log.time = item_history.time AND item = ANY(unroasted_id))), customer, reason FROM item_history(:item) WHERE time >= :sd AND time < :ed ::date + interval '1 day'");
225
+                    query.prepare("SELECT time::date, type, quantity / :c1, balance / :c2, (SELECT files FROM roasting_log WHERE roasting_log.time = item_history.time AND item = ANY(unroasted_id)), (SELECT invoice_id FROM invoice_items WHERE item = item_id AND item_history.type = 'PURCHASE'), (SELECT vendor || ' ' || invoice FROM invoices WHERE id = (SELECT invoice_id FROM invoice_items WHERE item = item_id AND item_history.type = 'PURCHASE')), (SELECT name FROM items WHERE id = (SELECT roasted_id FROM roasting_log WHERE roasting_log.time = item_history.time AND item = ANY(unroasted_id))), customer, reason, (SELECT person FROM transactions WHERE time = item_history.time AND item = item_history.item) FROM item_history(:item) WHERE time >= :sd AND time < :ed ::date + interval '1 day'");
226 226
                     query.bind(":sd", startDate);
227 227
                     query.bind(":ed", endDate);
228 228
                     switch(unitBox.currentIndex)
@@ -245,6 +245,7 @@
245 245
                     output.writeTextElement("th", TTR("item_transactions", "Quantity"));
246 246
                     output.writeTextElement("th", TTR("item_transactions", "Balance"));
247 247
                     output.writeTextElement("th", TTR("item_transactions", "Record"));
248
+					output.writeTextElement("th", TTR("item_transactions", "Person"));
248 249
                     output.writeEndElement(); // tr
249 250
                     var prev_balance = "0";
250 251
                     var prev_prec = 0;
@@ -297,6 +298,7 @@
297 298
                         } else {
298 299
                             output.writeTextElement("td", "");
299 300
                         }
301
+						output.writeTextElement("td", query.value(10));
300 302
                         output.writeEndElement(); // tr
301 303
                     }
302 304
                     output.writeEndElement(); // table

+ 38
- 0
config/Scripts/batchtag.css 查看文件

@@ -0,0 +1,38 @@
1
+body, h1, h2, h3, h4, h5, h6,
2
+p, blockquote, pre, hr,
3
+dl, dd, ol, ul, figure {
4
+  margin: 0;
5
+  padding: 0;
6
+}
7
+*,
8
+*::before,
9
+*::after {
10
+    -webkit-box-sizing: border-box;
11
+    -moz-box-sizing: border-box;
12
+    box-sizing: border-box;
13
+}
14
+
15
+body {
16
+  width: 58mm;
17
+}
18
+
19
+h1 {
20
+  padding-top: 10mm;
21
+  font-weight: 400;
22
+  font-size: 5mm;
23
+  text-align: center;
24
+}
25
+
26
+span {
27
+  display: block;
28
+  font-size: 4mm;
29
+  margin-left: 1mm;
30
+  margin-right: 1mm;
31
+}
32
+
33
+#container {
34
+	width: 190px;
35
+	margin-left: auto;
36
+	margin-right: auto;
37
+}
38
+

+ 346
- 0
config/Scripts/qrcode.js 查看文件

@@ -0,0 +1,346 @@
1
+/**
2
+ * @fileoverview
3
+ * - modified davidshimjs/qrcodejs library for use in node.js
4
+ * - Using the 'QRCode for Javascript library'
5
+ * - Fixed dataset of 'QRCode for Javascript library' for support full-spec.
6
+ * - this library has no dependencies.
7
+ *
8
+ * @version 0.9.1 (2016-02-12)
9
+ * @author davidshimjs, papnkukn
10
+ * @see <a href="http://www.d-project.com/" target="_blank">http://www.d-project.com/</a>
11
+ * @see <a href="http://jeromeetienne.github.com/jquery-qrcode/" target="_blank">http://jeromeetienne.github.com/jquery-qrcode/</a>
12
+ * @see <a href="https://github.com/davidshimjs/qrcodejs" target="_blank">https://github.com/davidshimjs/qrcodejs</a>
13
+ */
14
+
15
+//---------------------------------------------------------------------
16
+// QRCode for JavaScript
17
+//
18
+// Copyright (c) 2009 Kazuhiko Arase
19
+//
20
+// URL: http://www.d-project.com/
21
+//
22
+// Licensed under the MIT license:
23
+//   http://www.opensource.org/licenses/mit-license.php
24
+//
25
+// The word "QR Code" is registered trademark of 
26
+// DENSO WAVE INCORPORATED
27
+//   http://www.denso-wave.com/qrcode/faqpatent-e.html
28
+//
29
+// Modified by Neal Wilson to replace the width and height attributes
30
+// of the generated svg with a viewbox for easier scaling.
31
+//
32
+//---------------------------------------------------------------------
33
+function QR8bitByte(data) {
34
+  this.mode = QRMode.MODE_8BIT_BYTE;
35
+  this.data = data;
36
+  this.parsedData = [];
37
+
38
+  // Added to support UTF-8 Characters
39
+  for (var i = 0, l = this.data.length; i < l; i++) {
40
+    var byteArray = [];
41
+    var code = this.data.charCodeAt(i);
42
+
43
+    if (code > 0x10000) {
44
+      byteArray[0] = 0xF0 | ((code & 0x1C0000) >>> 18);
45
+      byteArray[1] = 0x80 | ((code & 0x3F000) >>> 12);
46
+      byteArray[2] = 0x80 | ((code & 0xFC0) >>> 6);
47
+      byteArray[3] = 0x80 | (code & 0x3F);
48
+    } else if (code > 0x800) {
49
+      byteArray[0] = 0xE0 | ((code & 0xF000) >>> 12);
50
+      byteArray[1] = 0x80 | ((code & 0xFC0) >>> 6);
51
+      byteArray[2] = 0x80 | (code & 0x3F);
52
+    } else if (code > 0x80) {
53
+      byteArray[0] = 0xC0 | ((code & 0x7C0) >>> 6);
54
+      byteArray[1] = 0x80 | (code & 0x3F);
55
+    } else {
56
+      byteArray[0] = code;
57
+    }
58
+
59
+    this.parsedData.push(byteArray);
60
+  }
61
+
62
+  this.parsedData = Array.prototype.concat.apply([], this.parsedData);
63
+
64
+  if (this.parsedData.length != this.data.length) {
65
+    this.parsedData.unshift(191);
66
+    this.parsedData.unshift(187);
67
+    this.parsedData.unshift(239);
68
+  }
69
+}
70
+
71
+QR8bitByte.prototype = {
72
+  getLength: function (buffer) {
73
+    return this.parsedData.length;
74
+  },
75
+  write: function (buffer) {
76
+    for (var i = 0, l = this.parsedData.length; i < l; i++) {
77
+      buffer.put(this.parsedData[i], 8);
78
+    }
79
+  }
80
+};
81
+
82
+function QRCodeModel(typeNumber, errorCorrectLevel) {
83
+  this.typeNumber = typeNumber;
84
+  this.errorCorrectLevel = errorCorrectLevel;
85
+  this.modules = null;
86
+  this.moduleCount = 0;
87
+  this.dataCache = null;
88
+  this.dataList = [];
89
+}
90
+
91
+QRCodeModel.prototype={addData:function(data){var newData=new QR8bitByte(data);this.dataList.push(newData);this.dataCache=null;},isDark:function(row,col){if(row<0||this.moduleCount<=row||col<0||this.moduleCount<=col){throw new Error(row+","+col);}
92
+return this.modules[row][col];},getModuleCount:function(){return this.moduleCount;},make:function(){this.makeImpl(false,this.getBestMaskPattern());},makeImpl:function(test,maskPattern){this.moduleCount=this.typeNumber*4+17;this.modules=new Array(this.moduleCount);for(var row=0;row<this.moduleCount;row++){this.modules[row]=new Array(this.moduleCount);for(var col=0;col<this.moduleCount;col++){this.modules[row][col]=null;}}
93
+this.setupPositionProbePattern(0,0);this.setupPositionProbePattern(this.moduleCount-7,0);this.setupPositionProbePattern(0,this.moduleCount-7);this.setupPositionAdjustPattern();this.setupTimingPattern();this.setupTypeInfo(test,maskPattern);if(this.typeNumber>=7){this.setupTypeNumber(test);}
94
+if(this.dataCache==null){this.dataCache=QRCodeModel.createData(this.typeNumber,this.errorCorrectLevel,this.dataList);}
95
+this.mapData(this.dataCache,maskPattern);},setupPositionProbePattern:function(row,col){for(var r=-1;r<=7;r++){if(row+r<=-1||this.moduleCount<=row+r)continue;for(var c=-1;c<=7;c++){if(col+c<=-1||this.moduleCount<=col+c)continue;if((0<=r&&r<=6&&(c==0||c==6))||(0<=c&&c<=6&&(r==0||r==6))||(2<=r&&r<=4&&2<=c&&c<=4)){this.modules[row+r][col+c]=true;}else{this.modules[row+r][col+c]=false;}}}},getBestMaskPattern:function(){var minLostPoint=0;var pattern=0;for(var i=0;i<8;i++){this.makeImpl(true,i);var lostPoint=QRUtil.getLostPoint(this);if(i==0||minLostPoint>lostPoint){minLostPoint=lostPoint;pattern=i;}}
96
+return pattern;},createMovieClip:function(target_mc,instance_name,depth){var qr_mc=target_mc.createEmptyMovieClip(instance_name,depth);var cs=1;this.make();for(var row=0;row<this.modules.length;row++){var y=row*cs;for(var col=0;col<this.modules[row].length;col++){var x=col*cs;var dark=this.modules[row][col];if(dark){qr_mc.beginFill(0,100);qr_mc.moveTo(x,y);qr_mc.lineTo(x+cs,y);qr_mc.lineTo(x+cs,y+cs);qr_mc.lineTo(x,y+cs);qr_mc.endFill();}}}
97
+return qr_mc;},setupTimingPattern:function(){for(var r=8;r<this.moduleCount-8;r++){if(this.modules[r][6]!=null){continue;}
98
+this.modules[r][6]=(r%2==0);}
99
+for(var c=8;c<this.moduleCount-8;c++){if(this.modules[6][c]!=null){continue;}
100
+this.modules[6][c]=(c%2==0);}},setupPositionAdjustPattern:function(){var pos=QRUtil.getPatternPosition(this.typeNumber);for(var i=0;i<pos.length;i++){for(var j=0;j<pos.length;j++){var row=pos[i];var col=pos[j];if(this.modules[row][col]!=null){continue;}
101
+for(var r=-2;r<=2;r++){for(var c=-2;c<=2;c++){if(r==-2||r==2||c==-2||c==2||(r==0&&c==0)){this.modules[row+r][col+c]=true;}else{this.modules[row+r][col+c]=false;}}}}}},setupTypeNumber:function(test){var bits=QRUtil.getBCHTypeNumber(this.typeNumber);for(var i=0;i<18;i++){var mod=(!test&&((bits>>i)&1)==1);this.modules[Math.floor(i/3)][i%3+this.moduleCount-8-3]=mod;}
102
+for(var i=0;i<18;i++){var mod=(!test&&((bits>>i)&1)==1);this.modules[i%3+this.moduleCount-8-3][Math.floor(i/3)]=mod;}},setupTypeInfo:function(test,maskPattern){var data=(this.errorCorrectLevel<<3)|maskPattern;var bits=QRUtil.getBCHTypeInfo(data);for(var i=0;i<15;i++){var mod=(!test&&((bits>>i)&1)==1);if(i<6){this.modules[i][8]=mod;}else if(i<8){this.modules[i+1][8]=mod;}else{this.modules[this.moduleCount-15+i][8]=mod;}}
103
+for(var i=0;i<15;i++){var mod=(!test&&((bits>>i)&1)==1);if(i<8){this.modules[8][this.moduleCount-i-1]=mod;}else if(i<9){this.modules[8][15-i-1+1]=mod;}else{this.modules[8][15-i-1]=mod;}}
104
+this.modules[this.moduleCount-8][8]=(!test);},mapData:function(data,maskPattern){var inc=-1;var row=this.moduleCount-1;var bitIndex=7;var byteIndex=0;for(var col=this.moduleCount-1;col>0;col-=2){if(col==6)col--;while(true){for(var c=0;c<2;c++){if(this.modules[row][col-c]==null){var dark=false;if(byteIndex<data.length){dark=(((data[byteIndex]>>>bitIndex)&1)==1);}
105
+var mask=QRUtil.getMask(maskPattern,row,col-c);if(mask){dark=!dark;}
106
+this.modules[row][col-c]=dark;bitIndex--;if(bitIndex==-1){byteIndex++;bitIndex=7;}}}
107
+row+=inc;if(row<0||this.moduleCount<=row){row-=inc;inc=-inc;break;}}}}};QRCodeModel.PAD0=0xEC;QRCodeModel.PAD1=0x11;QRCodeModel.createData=function(typeNumber,errorCorrectLevel,dataList){var rsBlocks=QRRSBlock.getRSBlocks(typeNumber,errorCorrectLevel);var buffer=new QRBitBuffer();for(var i=0;i<dataList.length;i++){var data=dataList[i];buffer.put(data.mode,4);buffer.put(data.getLength(),QRUtil.getLengthInBits(data.mode,typeNumber));data.write(buffer);}
108
+var totalDataCount=0;for(var i=0;i<rsBlocks.length;i++){totalDataCount+=rsBlocks[i].dataCount;}
109
+if(buffer.getLengthInBits()>totalDataCount*8){throw new Error("code length overflow. ("
110
++buffer.getLengthInBits()
111
++">"
112
++totalDataCount*8
113
++")");}
114
+if(buffer.getLengthInBits()+4<=totalDataCount*8){buffer.put(0,4);}
115
+while(buffer.getLengthInBits()%8!=0){buffer.putBit(false);}
116
+while(true){if(buffer.getLengthInBits()>=totalDataCount*8){break;}
117
+buffer.put(QRCodeModel.PAD0,8);if(buffer.getLengthInBits()>=totalDataCount*8){break;}
118
+buffer.put(QRCodeModel.PAD1,8);}
119
+return QRCodeModel.createBytes(buffer,rsBlocks);};QRCodeModel.createBytes=function(buffer,rsBlocks){var offset=0;var maxDcCount=0;var maxEcCount=0;var dcdata=new Array(rsBlocks.length);var ecdata=new Array(rsBlocks.length);for(var r=0;r<rsBlocks.length;r++){var dcCount=rsBlocks[r].dataCount;var ecCount=rsBlocks[r].totalCount-dcCount;maxDcCount=Math.max(maxDcCount,dcCount);maxEcCount=Math.max(maxEcCount,ecCount);dcdata[r]=new Array(dcCount);for(var i=0;i<dcdata[r].length;i++){dcdata[r][i]=0xff&buffer.buffer[i+offset];}
120
+offset+=dcCount;var rsPoly=QRUtil.getErrorCorrectPolynomial(ecCount);var rawPoly=new QRPolynomial(dcdata[r],rsPoly.getLength()-1);var modPoly=rawPoly.mod(rsPoly);ecdata[r]=new Array(rsPoly.getLength()-1);for(var i=0;i<ecdata[r].length;i++){var modIndex=i+modPoly.getLength()-ecdata[r].length;ecdata[r][i]=(modIndex>=0)?modPoly.get(modIndex):0;}}
121
+var totalCodeCount=0;for(var i=0;i<rsBlocks.length;i++){totalCodeCount+=rsBlocks[i].totalCount;}
122
+var data=new Array(totalCodeCount);var index=0;for(var i=0;i<maxDcCount;i++){for(var r=0;r<rsBlocks.length;r++){if(i<dcdata[r].length){data[index++]=dcdata[r][i];}}}
123
+for(var i=0;i<maxEcCount;i++){for(var r=0;r<rsBlocks.length;r++){if(i<ecdata[r].length){data[index++]=ecdata[r][i];}}}
124
+return data;};var QRMode={MODE_NUMBER:1<<0,MODE_ALPHA_NUM:1<<1,MODE_8BIT_BYTE:1<<2,MODE_KANJI:1<<3};var QRErrorCorrectLevel={L:1,M:0,Q:3,H:2};var QRMaskPattern={PATTERN000:0,PATTERN001:1,PATTERN010:2,PATTERN011:3,PATTERN100:4,PATTERN101:5,PATTERN110:6,PATTERN111:7};var QRUtil={PATTERN_POSITION_TABLE:[[],[6,18],[6,22],[6,26],[6,30],[6,34],[6,22,38],[6,24,42],[6,26,46],[6,28,50],[6,30,54],[6,32,58],[6,34,62],[6,26,46,66],[6,26,48,70],[6,26,50,74],[6,30,54,78],[6,30,56,82],[6,30,58,86],[6,34,62,90],[6,28,50,72,94],[6,26,50,74,98],[6,30,54,78,102],[6,28,54,80,106],[6,32,58,84,110],[6,30,58,86,114],[6,34,62,90,118],[6,26,50,74,98,122],[6,30,54,78,102,126],[6,26,52,78,104,130],[6,30,56,82,108,134],[6,34,60,86,112,138],[6,30,58,86,114,142],[6,34,62,90,118,146],[6,30,54,78,102,126,150],[6,24,50,76,102,128,154],[6,28,54,80,106,132,158],[6,32,58,84,110,136,162],[6,26,54,82,110,138,166],[6,30,58,86,114,142,170]],G15:(1<<10)|(1<<8)|(1<<5)|(1<<4)|(1<<2)|(1<<1)|(1<<0),G18:(1<<12)|(1<<11)|(1<<10)|(1<<9)|(1<<8)|(1<<5)|(1<<2)|(1<<0),G15_MASK:(1<<14)|(1<<12)|(1<<10)|(1<<4)|(1<<1),getBCHTypeInfo:function(data){var d=data<<10;while(QRUtil.getBCHDigit(d)-QRUtil.getBCHDigit(QRUtil.G15)>=0){d^=(QRUtil.G15<<(QRUtil.getBCHDigit(d)-QRUtil.getBCHDigit(QRUtil.G15)));}
125
+return((data<<10)|d)^QRUtil.G15_MASK;},getBCHTypeNumber:function(data){var d=data<<12;while(QRUtil.getBCHDigit(d)-QRUtil.getBCHDigit(QRUtil.G18)>=0){d^=(QRUtil.G18<<(QRUtil.getBCHDigit(d)-QRUtil.getBCHDigit(QRUtil.G18)));}
126
+return(data<<12)|d;},getBCHDigit:function(data){var digit=0;while(data!=0){digit++;data>>>=1;}
127
+return digit;},getPatternPosition:function(typeNumber){return QRUtil.PATTERN_POSITION_TABLE[typeNumber-1];},getMask:function(maskPattern,i,j){switch(maskPattern){case QRMaskPattern.PATTERN000:return(i+j)%2==0;case QRMaskPattern.PATTERN001:return i%2==0;case QRMaskPattern.PATTERN010:return j%3==0;case QRMaskPattern.PATTERN011:return(i+j)%3==0;case QRMaskPattern.PATTERN100:return(Math.floor(i/2)+Math.floor(j/3))%2==0;case QRMaskPattern.PATTERN101:return(i*j)%2+(i*j)%3==0;case QRMaskPattern.PATTERN110:return((i*j)%2+(i*j)%3)%2==0;case QRMaskPattern.PATTERN111:return((i*j)%3+(i+j)%2)%2==0;default:throw new Error("bad maskPattern:"+maskPattern);}},getErrorCorrectPolynomial:function(errorCorrectLength){var a=new QRPolynomial([1],0);for(var i=0;i<errorCorrectLength;i++){a=a.multiply(new QRPolynomial([1,QRMath.gexp(i)],0));}
128
+return a;},getLengthInBits:function(mode,type){if(1<=type&&type<10){switch(mode){case QRMode.MODE_NUMBER:return 10;case QRMode.MODE_ALPHA_NUM:return 9;case QRMode.MODE_8BIT_BYTE:return 8;case QRMode.MODE_KANJI:return 8;default:throw new Error("mode:"+mode);}}else if(type<27){switch(mode){case QRMode.MODE_NUMBER:return 12;case QRMode.MODE_ALPHA_NUM:return 11;case QRMode.MODE_8BIT_BYTE:return 16;case QRMode.MODE_KANJI:return 10;default:throw new Error("mode:"+mode);}}else if(type<41){switch(mode){case QRMode.MODE_NUMBER:return 14;case QRMode.MODE_ALPHA_NUM:return 13;case QRMode.MODE_8BIT_BYTE:return 16;case QRMode.MODE_KANJI:return 12;default:throw new Error("mode:"+mode);}}else{throw new Error("type:"+type);}},getLostPoint:function(qrCode){var moduleCount=qrCode.getModuleCount();var lostPoint=0;for(var row=0;row<moduleCount;row++){for(var col=0;col<moduleCount;col++){var sameCount=0;var dark=qrCode.isDark(row,col);for(var r=-1;r<=1;r++){if(row+r<0||moduleCount<=row+r){continue;}
129
+for(var c=-1;c<=1;c++){if(col+c<0||moduleCount<=col+c){continue;}
130
+if(r==0&&c==0){continue;}
131
+if(dark==qrCode.isDark(row+r,col+c)){sameCount++;}}}
132
+if(sameCount>5){lostPoint+=(3+sameCount-5);}}}
133
+for(var row=0;row<moduleCount-1;row++){for(var col=0;col<moduleCount-1;col++){var count=0;if(qrCode.isDark(row,col))count++;if(qrCode.isDark(row+1,col))count++;if(qrCode.isDark(row,col+1))count++;if(qrCode.isDark(row+1,col+1))count++;if(count==0||count==4){lostPoint+=3;}}}
134
+for(var row=0;row<moduleCount;row++){for(var col=0;col<moduleCount-6;col++){if(qrCode.isDark(row,col)&&!qrCode.isDark(row,col+1)&&qrCode.isDark(row,col+2)&&qrCode.isDark(row,col+3)&&qrCode.isDark(row,col+4)&&!qrCode.isDark(row,col+5)&&qrCode.isDark(row,col+6)){lostPoint+=40;}}}
135
+for(var col=0;col<moduleCount;col++){for(var row=0;row<moduleCount-6;row++){if(qrCode.isDark(row,col)&&!qrCode.isDark(row+1,col)&&qrCode.isDark(row+2,col)&&qrCode.isDark(row+3,col)&&qrCode.isDark(row+4,col)&&!qrCode.isDark(row+5,col)&&qrCode.isDark(row+6,col)){lostPoint+=40;}}}
136
+var darkCount=0;for(var col=0;col<moduleCount;col++){for(var row=0;row<moduleCount;row++){if(qrCode.isDark(row,col)){darkCount++;}}}
137
+var ratio=Math.abs(100*darkCount/moduleCount/moduleCount-50)/5;lostPoint+=ratio*10;return lostPoint;}};var QRMath={glog:function(n){if(n<1){throw new Error("glog("+n+")");}
138
+return QRMath.LOG_TABLE[n];},gexp:function(n){while(n<0){n+=255;}
139
+while(n>=256){n-=255;}
140
+return QRMath.EXP_TABLE[n];},EXP_TABLE:new Array(256),LOG_TABLE:new Array(256)};for(var i=0;i<8;i++){QRMath.EXP_TABLE[i]=1<<i;}
141
+for(var i=8;i<256;i++){QRMath.EXP_TABLE[i]=QRMath.EXP_TABLE[i-4]^QRMath.EXP_TABLE[i-5]^QRMath.EXP_TABLE[i-6]^QRMath.EXP_TABLE[i-8];}
142
+for(var i=0;i<255;i++){QRMath.LOG_TABLE[QRMath.EXP_TABLE[i]]=i;}
143
+function QRPolynomial(num,shift){if(num.length==undefined){throw new Error(num.length+"/"+shift);}
144
+var offset=0;while(offset<num.length&&num[offset]==0){offset++;}
145
+this.num=new Array(num.length-offset+shift);for(var i=0;i<num.length-offset;i++){this.num[i]=num[i+offset];}}
146
+QRPolynomial.prototype={get:function(index){return this.num[index];},getLength:function(){return this.num.length;},multiply:function(e){var num=new Array(this.getLength()+e.getLength()-1);for(var i=0;i<this.getLength();i++){for(var j=0;j<e.getLength();j++){num[i+j]^=QRMath.gexp(QRMath.glog(this.get(i))+QRMath.glog(e.get(j)));}}
147
+return new QRPolynomial(num,0);},mod:function(e){if(this.getLength()-e.getLength()<0){return this;}
148
+var ratio=QRMath.glog(this.get(0))-QRMath.glog(e.get(0));var num=new Array(this.getLength());for(var i=0;i<this.getLength();i++){num[i]=this.get(i);}
149
+for(var i=0;i<e.getLength();i++){num[i]^=QRMath.gexp(QRMath.glog(e.get(i))+ratio);}
150
+return new QRPolynomial(num,0).mod(e);}};function QRRSBlock(totalCount,dataCount){this.totalCount=totalCount;this.dataCount=dataCount;}
151
+QRRSBlock.RS_BLOCK_TABLE=[[1,26,19],[1,26,16],[1,26,13],[1,26,9],[1,44,34],[1,44,28],[1,44,22],[1,44,16],[1,70,55],[1,70,44],[2,35,17],[2,35,13],[1,100,80],[2,50,32],[2,50,24],[4,25,9],[1,134,108],[2,67,43],[2,33,15,2,34,16],[2,33,11,2,34,12],[2,86,68],[4,43,27],[4,43,19],[4,43,15],[2,98,78],[4,49,31],[2,32,14,4,33,15],[4,39,13,1,40,14],[2,121,97],[2,60,38,2,61,39],[4,40,18,2,41,19],[4,40,14,2,41,15],[2,146,116],[3,58,36,2,59,37],[4,36,16,4,37,17],[4,36,12,4,37,13],[2,86,68,2,87,69],[4,69,43,1,70,44],[6,43,19,2,44,20],[6,43,15,2,44,16],[4,101,81],[1,80,50,4,81,51],[4,50,22,4,51,23],[3,36,12,8,37,13],[2,116,92,2,117,93],[6,58,36,2,59,37],[4,46,20,6,47,21],[7,42,14,4,43,15],[4,133,107],[8,59,37,1,60,38],[8,44,20,4,45,21],[12,33,11,4,34,12],[3,145,115,1,146,116],[4,64,40,5,65,41],[11,36,16,5,37,17],[11,36,12,5,37,13],[5,109,87,1,110,88],[5,65,41,5,66,42],[5,54,24,7,55,25],[11,36,12],[5,122,98,1,123,99],[7,73,45,3,74,46],[15,43,19,2,44,20],[3,45,15,13,46,16],[1,135,107,5,136,108],[10,74,46,1,75,47],[1,50,22,15,51,23],[2,42,14,17,43,15],[5,150,120,1,151,121],[9,69,43,4,70,44],[17,50,22,1,51,23],[2,42,14,19,43,15],[3,141,113,4,142,114],[3,70,44,11,71,45],[17,47,21,4,48,22],[9,39,13,16,40,14],[3,135,107,5,136,108],[3,67,41,13,68,42],[15,54,24,5,55,25],[15,43,15,10,44,16],[4,144,116,4,145,117],[17,68,42],[17,50,22,6,51,23],[19,46,16,6,47,17],[2,139,111,7,140,112],[17,74,46],[7,54,24,16,55,25],[34,37,13],[4,151,121,5,152,122],[4,75,47,14,76,48],[11,54,24,14,55,25],[16,45,15,14,46,16],[6,147,117,4,148,118],[6,73,45,14,74,46],[11,54,24,16,55,25],[30,46,16,2,47,17],[8,132,106,4,133,107],[8,75,47,13,76,48],[7,54,24,22,55,25],[22,45,15,13,46,16],[10,142,114,2,143,115],[19,74,46,4,75,47],[28,50,22,6,51,23],[33,46,16,4,47,17],[8,152,122,4,153,123],[22,73,45,3,74,46],[8,53,23,26,54,24],[12,45,15,28,46,16],[3,147,117,10,148,118],[3,73,45,23,74,46],[4,54,24,31,55,25],[11,45,15,31,46,16],[7,146,116,7,147,117],[21,73,45,7,74,46],[1,53,23,37,54,24],[19,45,15,26,46,16],[5,145,115,10,146,116],[19,75,47,10,76,48],[15,54,24,25,55,25],[23,45,15,25,46,16],[13,145,115,3,146,116],[2,74,46,29,75,47],[42,54,24,1,55,25],[23,45,15,28,46,16],[17,145,115],[10,74,46,23,75,47],[10,54,24,35,55,25],[19,45,15,35,46,16],[17,145,115,1,146,116],[14,74,46,21,75,47],[29,54,24,19,55,25],[11,45,15,46,46,16],[13,145,115,6,146,116],[14,74,46,23,75,47],[44,54,24,7,55,25],[59,46,16,1,47,17],[12,151,121,7,152,122],[12,75,47,26,76,48],[39,54,24,14,55,25],[22,45,15,41,46,16],[6,151,121,14,152,122],[6,75,47,34,76,48],[46,54,24,10,55,25],[2,45,15,64,46,16],[17,152,122,4,153,123],[29,74,46,14,75,47],[49,54,24,10,55,25],[24,45,15,46,46,16],[4,152,122,18,153,123],[13,74,46,32,75,47],[48,54,24,14,55,25],[42,45,15,32,46,16],[20,147,117,4,148,118],[40,75,47,7,76,48],[43,54,24,22,55,25],[10,45,15,67,46,16],[19,148,118,6,149,119],[18,75,47,31,76,48],[34,54,24,34,55,25],[20,45,15,61,46,16]];QRRSBlock.getRSBlocks=function(typeNumber,errorCorrectLevel){var rsBlock=QRRSBlock.getRsBlockTable(typeNumber,errorCorrectLevel);if(rsBlock==undefined){throw new Error("bad rs block @ typeNumber:"+typeNumber+"/errorCorrectLevel:"+errorCorrectLevel);}
152
+var length=rsBlock.length/3;var list=[];for(var i=0;i<length;i++){var count=rsBlock[i*3+0];var totalCount=rsBlock[i*3+1];var dataCount=rsBlock[i*3+2];for(var j=0;j<count;j++){list.push(new QRRSBlock(totalCount,dataCount));}}
153
+return list;};QRRSBlock.getRsBlockTable=function(typeNumber,errorCorrectLevel){switch(errorCorrectLevel){case QRErrorCorrectLevel.L:return QRRSBlock.RS_BLOCK_TABLE[(typeNumber-1)*4+0];case QRErrorCorrectLevel.M:return QRRSBlock.RS_BLOCK_TABLE[(typeNumber-1)*4+1];case QRErrorCorrectLevel.Q:return QRRSBlock.RS_BLOCK_TABLE[(typeNumber-1)*4+2];case QRErrorCorrectLevel.H:return QRRSBlock.RS_BLOCK_TABLE[(typeNumber-1)*4+3];default:return undefined;}};function QRBitBuffer(){this.buffer=[];this.length=0;}
154
+QRBitBuffer.prototype={get:function(index){var bufIndex=Math.floor(index/8);return((this.buffer[bufIndex]>>>(7-index%8))&1)==1;},put:function(num,length){for(var i=0;i<length;i++){this.putBit(((num>>>(length-i-1))&1)==1);}},getLengthInBits:function(){return this.length;},putBit:function(bit){var bufIndex=Math.floor(this.length/8);if(this.buffer.length<=bufIndex){this.buffer.push(0);}
155
+if(bit){this.buffer[bufIndex]|=(0x80>>>(this.length%8));}
156
+this.length++;}};var QRCodeLimitLength=[[17,14,11,7],[32,26,20,14],[53,42,32,24],[78,62,46,34],[106,84,60,44],[134,106,74,58],[154,122,86,64],[192,152,108,84],[230,180,130,98],[271,213,151,119],[321,251,177,137],[367,287,203,155],[425,331,241,177],[458,362,258,194],[520,412,292,220],[586,450,322,250],[644,504,364,280],[718,560,394,310],[792,624,442,338],[858,666,482,382],[929,711,509,403],[1003,779,565,439],[1091,857,611,461],[1171,911,661,511],[1273,997,715,535],[1367,1059,751,593],[1465,1125,805,625],[1528,1190,868,658],[1628,1264,908,698],[1732,1370,982,742],[1840,1452,1030,790],[1952,1538,1112,842],[2068,1628,1168,898],[2188,1722,1228,958],[2303,1809,1283,983],[2431,1911,1351,1051],[2563,1989,1423,1093],[2699,2099,1499,1139],[2809,2213,1579,1219],[2953,2331,1663,1273]];
157
+
158
+
159
+/** Constructor */
160
+function QRCode(options) {
161
+  var instance = this;
162
+  
163
+  //Default options
164
+  this.options = {
165
+    padding: 4,
166
+    width: 256, 
167
+    height: 256,
168
+    typeNumber: 4,
169
+    color: "#000000",
170
+    background: "#ffffff",
171
+    ecl: "M"
172
+  };
173
+  
174
+  //In case the options is string
175
+  if (typeof options === 'string') {
176
+    options = {
177
+      content: options
178
+    };
179
+  }
180
+  
181
+  //Merge options
182
+  if (options) {
183
+    for (var i in options) {
184
+      this.options[i] = options[i];
185
+    }
186
+  }
187
+  
188
+  if (typeof this.options.content !== 'string') {
189
+    throw new Error("Expected 'content' as string!");
190
+  }
191
+  
192
+  if (this.options.content.length === 0 /* || this.options.content.length > 7089 */) {
193
+    throw new Error("Expected 'content' to be non-empty!");
194
+  }
195
+  
196
+  if (!(this.options.padding >= 0)) {
197
+    throw new Error("Expected 'padding' value to be non-negative!");
198
+  }
199
+  
200
+  if (!(this.options.width > 0) || !(this.options.height > 0)) {
201
+    throw new Error("Expected 'width' or 'height' value to be higher than zero!");
202
+  }
203
+  
204
+  //Gets the error correction level
205
+  function _getErrorCorrectLevel(ecl) {
206
+    switch (ecl) {
207
+        case "L":
208
+          return QRErrorCorrectLevel.L;
209
+          
210
+        case "M":
211
+          return QRErrorCorrectLevel.M;
212
+          
213
+        case "Q":
214
+          return QRErrorCorrectLevel.Q;
215
+          
216
+        case "H":
217
+          return QRErrorCorrectLevel.H;
218
+          
219
+        default:
220
+          throw new Error("Unknwon error correction level: " + ecl);
221
+      }
222
+  }
223
+  
224
+  //Get type number
225
+  function _getTypeNumber(content, ecl) {      
226
+    var length = _getUTF8Length(content);
227
+    
228
+    var type = 1;
229
+    var limit = 0;
230
+    for (var i = 0, len = QRCodeLimitLength.length; i <= len; i++) {
231
+      var table = QRCodeLimitLength[i];
232
+      if (!table) {
233
+        throw new Error("Content too long: expected " + limit + " but got " + length);
234
+      }
235
+      
236
+      switch (ecl) {
237
+        case "L":
238
+          limit = table[0];
239
+          break;
240
+          
241
+        case "M":
242
+          limit = table[1];
243
+          break;
244
+          
245
+        case "Q":
246
+          limit = table[2];
247
+          break;
248
+          
249
+        case "H":
250
+          limit = table[3];
251
+          break;
252
+          
253
+        default:
254
+          throw new Error("Unknwon error correction level: " + ecl);
255
+      }
256
+      
257
+      if (length <= limit) {
258
+        break;
259
+      }
260
+      
261
+      type++;
262
+    }
263
+    
264
+    if (type > QRCodeLimitLength.length) {
265
+      throw new Error("Content too long");
266
+    }
267
+    
268
+    return type;
269
+  }
270
+
271
+  //Gets text length
272
+  function _getUTF8Length(content) {
273
+    var result = encodeURI(content).toString().replace(/\%[0-9a-fA-F]{2}/g, 'a');
274
+    return result.length + (result.length != content ? 3 : 0);
275
+  }
276
+  
277
+  //Generate QR Code matrix
278
+  var content = this.options.content;
279
+  var type = _getTypeNumber(content, this.options.ecl);
280
+  var ecl = _getErrorCorrectLevel(this.options.ecl);
281
+  this.qrcode = new QRCodeModel(type, ecl);
282
+  this.qrcode.addData(content);
283
+  this.qrcode.make();
284
+}
285
+
286
+/** Generates QR Code as SVG image */
287
+QRCode.prototype.svg = function(opt) {
288
+  if (typeof opt == "undefined") {
289
+    opt = { container: "svg" };
290
+  }
291
+  
292
+  var options = this.options;
293
+  var modules = this.qrcode.modules;
294
+  
295
+  var EOL = '\r\n';
296
+  var width = options.width;
297
+  var height = options.height;
298
+  var length = modules.length;
299
+  var xsize = width / (length + 2 * options.padding);
300
+  var ysize = height / (length + 2 * options.padding);
301
+  
302
+  var rect = '<rect x="0" y="0" width="' + width + '" height="' + height + '" style="fill:' + options.background + ';shape-rendering:crispEdges;"/>' + EOL;
303
+
304
+  for (var y = 0; y < length; y++) {
305
+    for (var x = 0; x < length; x++) {
306
+      var module = modules[x][y];
307
+      if (module) {
308
+        var px = (x * xsize + options.padding * xsize).toString();
309
+        var py = (y * ysize + options.padding * ysize).toString();
310
+        rect += '<rect x="' + px + '" y="' + py + '" width="' + xsize + '" height="' + ysize + '" style="fill:' + options.color + ';shape-rendering:crispEdges;"/>' + EOL;
311
+      }
312
+    }
313
+  }
314
+
315
+  var svg = "";
316
+  switch (opt.container) {
317
+    case "svg":
318
+      svg += '<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewbox="0 0 ' + width + ' ' + height + '">' + EOL;
319
+      svg += rect;
320
+      svg += '</svg>';
321
+      break;
322
+      
323
+    case "g":
324
+      svg += '<g width="' + width + '" height="' + height + '">' + EOL;
325
+      svg += rect;
326
+      svg += '</g>';
327
+      break;
328
+      
329
+    default:
330
+      svg += rect;
331
+      break;
332
+  }
333
+  
334
+  return svg;
335
+};
336
+
337
+/** Writes QR Code image to a file */
338
+QRCode.prototype.save = function(file, callback) {
339
+  var data = this.svg();
340
+  var fs = require('fs');
341
+  fs.writeFile(file, data, callback);
342
+};
343
+
344
+if (typeof module != "undefined") {
345
+  module.exports = QRCode;
346
+}

+ 434
- 0
config/Windows/manuallogentry.xml 查看文件

@@ -0,0 +1,434 @@
1
+<window id="manualLogEntry">
2
+	<menu name="File">
3
+		<plugins id="pluginMenu" title="Import" src="ImportFilters" preRun="pluginContext.preRun();" postRun="pluginContext.postRun();"/>
4
+		<separator />
5
+		<item id="quitItem" shortcut="ctrl+Q">Quit</item>
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>
18
+	<layout type="vertical">
19
+		<tabbar id="tabs"/>
20
+		<layout type="stack" id="pages">
21
+			<page>
22
+				<layout type="vertical">
23
+					<layout type="horizontal">
24
+						<label>Batch Type:</label>
25
+						<sqldrop id="batchType" />
26
+						<label>Machine:</label>
27
+						<sqldrop id="machineSelector" />
28
+						<stretch />
29
+					</layout>
30
+					<label>Green Coffee:</label>
31
+					<layout type="stack" id="greenInfoLayout">
32
+						<page id="sampleGreen">
33
+							<layout type="vertical">
34
+								<layout type="grid">
35
+									<row>
36
+										<column><label>Name:</label></column>
37
+										<column><line id="sampleGreenName" /></column>
38
+									</row>
39
+									<row>
40
+										<column><label>Weight:</label></column>
41
+										<column><line id="sampleGreenWeight" validator="numeric">0.0</line></column>
42
+										<column><sqldrop id="sampleGreenUnit" /></column>
43
+									</row>
44
+									<row>
45
+										<column><label>Vendor:</label></column>
46
+										<column><line id="sampleGreenVendor" /></column>
47
+									</row>
48
+									<row>
49
+										<column><label>Arrival Date:</label></column>
50
+										<column><calendar id="sampleGreenArrivalDate" /></column>
51
+									</row>
52
+								</layout>
53
+								<label>Additional Details:</label>
54
+								<sqltablearray columns="2" id="attributes">
55
+									<column name="Attribute" />
56
+									<column name="Value" />
57
+								</sqltablearray>
58
+							</layout>
59
+						</page>
60
+						<page id="productionGreen">
61
+							<layout type="vertical">
62
+								<layout type="horizontal">
63
+									<label>Unit:</label>
64
+									<sqldrop id="productionGreenUnit" />
65
+									<stretch />
66
+								</layout>
67
+								<sqltablearray columns="2" id="productionGreenTable">
68
+									<column name="Coffee" delegate="sql" showdata="true" null="true" nulltext="Delete" nulldata="delete" data="0" display="1">
69
+										<![CDATA[SELECT id, name FROM coffees WHERE quantity <> 0 ORDER BY name]]>
70
+									</column>
71
+									<column name="Weight" delegate="numeric" />
72
+								</sqltablearray>
73
+							</layout>
74
+						</page>
75
+					</layout>
76
+					<label>Roasting Details:</label>
77
+					<layout type="grid">
78
+						<row>
79
+							<column><label>Item:</label></column>
80
+							<column>
81
+								<sqldrop data="0" display="1" showdata="true" id="roastedItem">
82
+									<null />
83
+									<query>SELECT id, name FROM items WHERE category = 'Coffee: Roasted' AND id IN (SELECT item FROM current_items) ORDER BY name</query>
84
+								</sqldrop>
85
+							</column>
86
+						</row>
87
+						<row>
88
+							<column><label>Weight:</label></column>
89
+							<column><line id="roastedWeight" validator="numeric">0.0</line></column>
90
+						</row>
91
+						<row>
92
+							<column><label>Time:</label></column>
93
+							<column><calendar id="roastTime" time="true"/></column>
94
+						</row>
95
+						<row>
96
+							<column><label>Duration:</label></column>
97
+							<column><timeedit id="roastDuration" /></column>
98
+						</row>
99
+						<row>
100
+							<column><label>Notes:</label></column>
101
+							<column><textarea id="notes" /></column>
102
+						</row>
103
+					</layout>
104
+				</layout>
105
+			</page>
106
+			<page>
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>
121
+					<splitter type="horizontal" id="roastdatasplit">
122
+						<measurementtable id="log" />
123
+						<graph id="graph" />
124
+					</splitter>
125
+				</layout>
126
+			</page>
127
+		</layout>
128
+		<layout type="horizontal">
129
+			<stretch />
130
+			<button name="Submit" id="submit" type="push" />
131
+		</layout>
132
+	</layout>
133
+	<program>
134
+	<![CDATA[
135
+		var window = this;
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
+		});
144
+		quitItem = findChildObject(this, 'quitItem');
145
+		quitItem.triggered.connect(function() {
146
+			Application.quit();
147
+		});
148
+		pluginContext = {};
149
+		pluginContext.table = findChildObject(this, 'log');
150
+		pluginContext.table.setHeaderData(1, "Temp");
151
+		pluginContext.table.setHeaderData(2, "Note");
152
+		pluginContext.graph = findChildObject(this, 'graph');
153
+		pluginContext.preRun = function() {
154
+			var filename = QFileDialog.getOpenFileName(window, TTR("manualLogEntry", "Import"), QSettings.value('script/lastDir', '') + '/');
155
+			var file = new QFile(filename);
156
+			if(file.open(1)) {
157
+				pluginContext.data = file.readToString();
158
+				file.close();
159
+				pluginContext.table.clear();
160
+				pluginContext.graph.clear();
161
+				QSettings.setValue("script/lastDir", dir(filename));
162
+			} else {
163
+				throw new Error("Failed to open file, aborting import.");
164
+			}
165
+		};
166
+		pluginContext.postRun = function() {
167
+			
168
+		};
169
+		pluginContext.newMeasurement = function(m, c) {
170
+			pluginContext.table.newMeasurement(m, c);
171
+			pluginContext.graph.newMeasurement(m, c);
172
+		}
173
+		pluginMenu = findChildObject(this, 'pluginMenu');
174
+		pluginMenu.setProperty("activationObject", pluginContext);
175
+		tabs = findChildObject(this, 'tabs');
176
+		tabs.addTab("Batch Data");
177
+		tabs.addTab("Roast Data");
178
+		pages = findChildObject(this, 'pages');
179
+		tabs.currentChanged.connect(function(index) {
180
+			pages.setCurrentIndex(index);
181
+		});
182
+		greenInfoLayout = findChildObject(this, 'greenInfoLayout');
183
+		roastedItem = findChildObject(this, 'roastedItem');
184
+		batchType = findChildObject(this, 'batchType');
185
+		batchType.addItem("Sample");
186
+		batchType.addItem("Production");
187
+		batchType['currentIndexChanged(int)'].connect(function(batchTypeIndex) {
188
+			QSettings.setValue("script/manual_batchType", batchTypeIndex);
189
+			greenInfoLayout.setCurrentIndex(batchTypeIndex);
190
+			roastedItem.enabled = (batchTypeIndex == 1);
191
+		});
192
+		batchType.setCurrentIndex(QSettings.value("script/manual_batchType", 1));
193
+		var machineSelector = findChildObject(this, 'machineSelector');
194
+        var machineModel = new DeviceTreeModel;
195
+        machineSelector.setModel(machineModel);
196
+        machineSelector.currentIndex = QSettings.value("script/manualMachineSelection", 0);
197
+        machineSelector['currentIndexChanged(int)'].connect(function(index) {
198
+            QSettings.setValue("script/manualMachineSelection", index);
199
+        });
200
+		sampleGreenUnit = findChildObject(this, 'sampleGreenUnit');
201
+		sampleGreenUnit.addItem("g");
202
+		sampleGreenUnit.addItem("Kg");
203
+		sampleGreenUnit.addItem("oz");
204
+		sampleGreenUnit.addItem("lb");
205
+		sampleGreenUnit.currentIndex = (QSettings.value("script/manual_unit", sampleGreenUnit.findText("lb")));
206
+		productionGreenUnit = findChildObject(this, 'productionGreenUnit');
207
+		productionGreenUnit.addItem("g");
208
+		productionGreenUnit.addItem("Kg");
209
+		productionGreenUnit.addItem("oz");
210
+		productionGreenUnit.addItem("lb");
211
+		productionGreenUnit.currentIndex = (QSettings.value("script/manual_unit", productionGreenUnit.findText("lb")));
212
+		sampleGreenUnit['currentIndexChanged(int)'].connect(function(greenUnitIndex) {
213
+			QSettings.setValue("script/manual_unit", greenUnitIndex);
214
+			productionGreenUnit.setCurrentIndex(greenUnitIndex);
215
+		});
216
+		productionGreenUnit['currentIndexChanged(int)'].connect(function(greenUnitIndex) {
217
+			QSettings.setValue("script/manual_unit", greenUnitIndex);
218
+			sampleGreenUnit.setCurrentIndex(greenUnitIndex);
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 roastTime = findChildObject(this, 'roastTime');
323
+		var attributes = findChildObject(this, 'attributes');
324
+		var sampleGreenArrivalDate = findChildObject(this, 'sampleGreenArrivalDate');
325
+		var convertToPounds = function(w, u) {
326
+			switch(u) {
327
+				case "g":
328
+					return w * 0.0022;
329
+				case "oz":
330
+					return w * 0.0625;
331
+				case "Kg":
332
+					return w * 2.2;
333
+			}
334
+			return w;
335
+		};
336
+		var roastedWeight = findChildObject(this, 'roastedWeight');
337
+		var notes = findChildObject(this, 'notes');
338
+		var roastDuration = findChildObject(this, 'roastDuration');
339
+		var doSubmit = function() {
340
+			var fileID = -1;
341
+			var query = new QSqlQuery();
342
+			if(roastDataExists()) {
343
+				var buffer = new QBuffer;
344
+				buffer.open(3);
345
+				pluginContext.table.saveXML(buffer);
346
+				var q = "INSERT INTO files (id, name, type, note, file) VALUES (default, :name, 'profile', NULL, :data) RETURNING id";
347
+				query.prepare(q);
348
+				query.bind(":name", roastTime.text + " Manual Entry");
349
+				query.bindDeviceData(":data", buffer);
350
+				query.exec();
351
+				query.next();
352
+				fileID = Number(query.value(0));
353
+			}
354
+			var rootIndex = machineModel.index(machineSelector.currentIndex, 0);
355
+			var selectedRoasterName = machineModel.data(rootIndex, 0);
356
+			var machineReference = machineModel.referenceElement(machineModel.data(rootIndex, 32));
357
+			var selectedRoasterID = machineReference.databaseid;
358
+			query.exec("SELECT 1 FROM machine WHERE id = " + selectedRoasterID);
359
+			if(!query.next()) {
360
+				query.prepare("INSERT INTO machine (id, name) VALUES (:id, :name)");
361
+				query.bind(":id", selectedRoasterID);
362
+				query.bind(":name", selectedRoasterName);
363
+				query.exec();
364
+			}
365
+			if(batchType.currentIndex == 0) {
366
+				/* Sample roast */
367
+				var attnames = sqlToArray(attributes.columnArray(0, 0));
368
+				for(var i = 0; i < attnames.length; i++) {
369
+					var attname = attnames[i];
370
+					if(attname[0] == '{') {
371
+						attname = attname.substr(1);
372
+					}
373
+					if(attname[0] == ' ') {
374
+						attname = attname.substr(1);
375
+					}
376
+					if(attname[attname.length - 1] == '}') {
377
+						attname = attname.substr(0, attname.length - 1);
378
+					}
379
+					if(attname.length == 0) {
380
+						break;
381
+					}
382
+					query.prepare("SELECT id FROM item_attributes WHERE name = :name");
383
+					query.bind(":name", attname);
384
+					query.exec();
385
+					if(query.next()) {
386
+						attributes.setData(i, 0, query.value(0), 32);
387
+					} else {
388
+						query.prepare("INSERT INTO item_attributes(id, name) VALUES (DEFAULT, :name) RETURNING id");
389
+						query.bind(":name", attname);
390
+						query.exec();
391
+						query.next();
392
+						attributes.setData(i, 0, query.value(0), 32);
393
+					}
394
+				}
395
+				query.prepare("INSERT INTO coffee_sample_items(id, name, reference, unit, quantity, category, arrival, vendor, attribute_ids, attribute_values) VALUES (DEFAULT, :name, NULL, 'lb', 0, 'Coffee: Green Sample', :arrival, :vendor, :attrids, :attrvals) RETURNING id");
396
+				query.bind(":name", sampleGreenName.text);
397
+				query.bind(":arrival", sampleGreenArrival.date);
398
+				query.bind(":attrids", attributes.bindableColumnArray(0, 32));
399
+				query.bind(":attrvals", attributes.bindableQuotedColumnArray(1, 0));
400
+				query.exec();
401
+				query.next();
402
+				var greenId = query.value(0);
403
+				query.prepare("INSERT INTO items (id, name, reference, unit, quantity, category) VALUES (DEFAULT, :name, NULL, 'lb', 0, 'Coffee: Roasted Sample') RETURNING id");
404
+				query.bind(":name", sampleGreenName.text + " Roasted Sample");
405
+				query.exec();
406
+				query.next();
407
+				var roastedId = query.value(0);
408
+				query.prepare("INSERT INTO roasting_log (time, unroasted_id, unroasted_quantity, unroasted_total_quantity, roasted_id, roasted_quantity, transaction_type, annotation, machine, duration, approval, humidity, barometric, indoor_air, outdoor_air, files) VALUES (:time, :unroastedids, :greens, :green, :roastedid, :roasted, 'SAMPLEROAST', :note, :machine, :duration, TRUE, NULL, NULL, NULL, NULL, :files)");
409
+				query.bind(":time", roastTime.text);
410
+				query.bind(":unroastedids", "{" + greenId + "}");
411
+				query.bind(":greens", "{" + convertToPounds(parseFloat(sampleGreenWeight.text), sampleGreenUnit.currentText) + "}");
412
+				query.bind(":green", convertToPounds(parseFloat(sampleGreenWeight.text), sampleGreenUnit.currentText));
413
+				query.bind("roastedid", Number(roastedId));
414
+				query.bind("roasted", convertToPounds(parseFloat(roastedWeight.text), sampleGreenUnit.currentText));
415
+				query.bind(":note", notes.plainText);
416
+				query.bind(":machine", Number(selectedRoasterID));
417
+				query.bind(":duration", roastDuration.text);
418
+				query.bind(":files", "{" + fileID + "}");
419
+				query.exec();
420
+			} else {
421
+				/* Production roast */
422
+			}
423
+			query = query.invalidate();
424
+			window.close();
425
+		}
426
+		var submit = findChildObject(this, 'submit');
427
+		submit.clicked.connect(function() {
428
+			if(validateInputs()) {
429
+				doSubmit();
430
+			}
431
+		});
432
+	]]>
433
+	</program>
434
+</window>

+ 9
- 0
config/Windows/navigation.xml 查看文件

@@ -13,6 +13,11 @@
13 13
                 <button name="Roast Coffee" id="roast" type="push" />
14 14
             </column>
15 15
         </row>
16
+		<row>
17
+			<column>
18
+				<button name="Manual Roasting Log Entry" id="manual" type="push" />
19
+			</column>
20
+		</row>
16 21
         <row>
17 22
             <column>
18 23
                 <button name="Purchase Green Coffee" id="green" type="push" />
@@ -87,6 +92,10 @@
87 92
             QSettings.setValue("database/user", "");
88 93
             QSettings.setValue("database/password", "");
89 94
         });
95
+		var manual = findChildObject(this, 'manual');
96
+		manual.clicked.connect(function() {
97
+			createWindow("manualLogEntry");
98
+		});
90 99
         var profilehistory = findChildObject(this, 'profilehistory');
91 100
         profilehistory.clicked.connect(function() {
92 101
                 createWindow("profilehistory");

+ 79
- 1
config/Windows/newbatch.xml 查看文件

@@ -1,4 +1,7 @@
1 1
 <window id="batchWindow">
2
+	<menu name="File">
3
+		<item id="print" shortcut="Ctrl+P">Print...</item>
4
+	</menu>
2 5
     <menu name="Batch">
3 6
         <item id="new" shortcut="Ctrl+N">New Batch...</item>
4 7
     </menu>
@@ -81,6 +84,13 @@
81 84
             </layout>
82 85
             <stretch />
83 86
         </layout>
87
+		<layout type="vertical">
88
+			<webview id="batchTag" />
89
+			<layout type="horizontal">
90
+				<printerselector id="printerlist" />
91
+				<button name="Print" id="printbutton" type="push" />
92
+			</layout>
93
+		</layout>
84 94
     </layout>
85 95
     <program>
86 96
         <![CDATA[
@@ -111,6 +121,7 @@
111 121
             roastwt.maximumWidth = 80;
112 122
             var scalesLayout = findChildObject(this, 'scales');
113 123
             scalesLayout.spacing = 10;
124
+			var batchTag = findChildObject(this, 'batchTag');
114 125
             if(navigationwindow.loggingWindow.scales.length > 0) {
115 126
                 for(var i = 0; i < navigationwindow.loggingWindow.scales.length; i++) {
116 127
                     var scale = navigationwindow.loggingWindow.scales[i];
@@ -382,8 +393,8 @@
382 393
                     lossspec.text = "";
383 394
                     specnotes.plainText = "";
384 395
                 }
385
-                roastestimate.text = "";
386 396
                 query = query.invalidate();
397
+				drawTag();
387 398
             });
388 399
             var validateCapacity = function() {
389 400
                 if(checkCapacity == "true") {
@@ -559,6 +570,7 @@
559 570
                 filenofield.text = query.value(0);
560 571
                 var file = new QFile(batch.tempData);
561 572
                 file.remove();
573
+				drawTag();
562 574
             }
563 575
             var doSubmit = function() {
564 576
                 checkQuery = new QSqlQuery();
@@ -619,6 +631,72 @@
619 631
                 batch.windowModified = false;
620 632
                 batch.close();
621 633
             }
634
+			function drawTag() {
635
+				var buffer = new QBuffer;
636
+                buffer.open(3);
637
+                var output = new XmlWriter(buffer);
638
+                output.writeStartDocument("1.0");
639
+                output.writeDTD('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1 plus MathML 2.0 plus SVG 1.1//EN" "http://www.w3.org/2002/04/xhtml-math-svg.dtd">');
640
+                output.writeStartElement("html");
641
+                output.writeAttribute("xmlns", "http://www.w3.org/1999/xhtml");
642
+				output.writeStartElement("head");
643
+				var styleFile = new QFile(QSettings.value("config") + "/Scripts/batchtag.css");
644
+				styleFile.open(1);
645
+				output.writeTextElement("style", styleFile.readToString());
646
+				styleFile.close();
647
+				output.writeStartElement("script");
648
+				scriptFile = new QFile(QSettings.value("config") + "/Scripts/qrcode.js");
649
+				scriptFile.open(1);
650
+				output.writeCDATA(scriptFile.readToString());
651
+				scriptFile.close();
652
+				output.writeEndElement();
653
+				output.writeEndElement();
654
+                output.writeStartElement("body");
655
+				output.writeStartElement("h1");
656
+				output.writeCharacters(roasted.currentText);
657
+				output.writeEndElement();
658
+				output.writeTextElement("span", "Roasted at: " + timefield.text);
659
+				output.writeTextElement("span", "On machine: " + machine.text);
660
+				output.writeTextElement("span", "Batch file: " + filenofield.text);
661
+				output.writeStartElement("div");
662
+				output.writeAttribute("id", "container");
663
+				output.writeEndElement();
664
+				output.writeStartElement("script");
665
+				var tag = {g: "Typica", m: Number(selectedRoasterID), v: 1};
666
+				if(timefield.text.length > 0) {
667
+					tag.t = timefield.text;
668
+				}
669
+				if(filenofield.text.length > 0) {
670
+					tag.f = Number(filenofield.text);
671
+				}
672
+				var scriptData = 'var width = document.getElementById("container").offsetWidth;';
673
+				scriptData += 'var qrcode = new QRCode({content: \'';
674
+				scriptData += JSON.stringify(tag);
675
+				scriptData += '\', width: width, height: width});';
676
+				scriptData += 'var svg = qrcode.svg();';
677
+				scriptData += 'document.getElementById("container").innerHTML = svg;';
678
+				output.writeCDATA(scriptData);
679
+				output.writeEndElement();
680
+				output.writeEndElement();
681
+				output.writeEndElement();
682
+				output.writeEndDocument();
683
+                batchTag.setContent(buffer);
684
+                buffer.close();
685
+			};
686
+			drawTag();
687
+			var printMenu = findChildObject(this, 'print');
688
+            printMenu.triggered.connect(function() {
689
+                batchTag.print();
690
+            });
691
+			var printers = findChildObject(this, 'printerlist');
692
+			printers.currentIndex = printers.findText(QSettings.value("script/batchtagprinter"));
693
+			printers['currentIndexChanged(int)'].connect(function() {
694
+                QSettings.setValue("script/batchtagprinter", printers.currentText);
695
+            });
696
+			var printbutton = findChildObject(this, 'printbutton');
697
+			printbutton.clicked.connect(function() {
698
+				batchTag.print(printers.currentText);
699
+			});
622 700
         ]]>
623 701
     </program>
624 702
 </window>

+ 33
- 33
config/Windows/newsamplebatch.xml 查看文件

@@ -132,7 +132,7 @@
132 132
 			newMenu.triggered.connect(function() {
133 133
 				createWindow("sampleRoastingBatch");
134 134
 			});
135
-                        this.endBatch = function() {};
135
+            this.endBatch = function() {};
136 136
 			var batch = this;
137 137
 			batch.submitButton = submit;
138 138
 			var name = findChildObject(this, 'name');
@@ -180,27 +180,27 @@
180 180
 			stop.clicked.connect(function() {
181 181
 				submit.setEnabled(true);
182 182
 			});
183
-                        var validateCapacity = function() {
184
-                            if(checkCapacity == "true") {
185
-                                if(convertToPounds(parseFloat(green.text), GunitBox.currentText) > poundsCapacity) {
186
-                                    return false;
187
-                                }
188
-                            }
189
-                            return true;
190
-                        }
191
-                        roastButton.clicked.connect(function() {
192
-                            var proceed = false;
193
-                            if(validateCapacity()) {
194
-                                proceed = true;
195
-                            } else {
196
-                                proceed = displayWarning(TTR("sampleRoastingBatch", "Suspicious Input"),
183
+            var validateCapacity = function() {
184
+                if(checkCapacity == "true") {
185
+                    if(convertToPounds(parseFloat(green.text), GunitBox.currentText) > poundsCapacity) {
186
+                        return false;
187
+                    }
188
+                }
189
+                return true;
190
+            }
191
+            roastButton.clicked.connect(function() {
192
+                var proceed = false;
193
+                if(validateCapacity()) {
194
+                    proceed = true;
195
+                } else {
196
+                    proceed = displayWarning(TTR("sampleRoastingBatch", "Suspicious Input"),
197 197
                                 TTR("sampleRoastingBatch", "Entered green coffee weight exceeds maximum batch size. Continue?"));
198
-                            }
199
-                            if(proceed) {
200
-                                doRoast();
201
-                            }
202
-                        });
203
-                        var doRoast = function() {
198
+                }
199
+                if(proceed) {
200
+                    doRoast();
201
+                }
202
+                });
203
+                var doRoast = function() {
204 204
 				var lc = 1;
205 205
 				currentBatchInfo = batch;
206 206
 				query = new QSqlQuery();
@@ -271,19 +271,19 @@
271 271
 			var vendor = findChildObject(this, 'vendor');
272 272
 			var attributes = findChildObject(this, 'attributes');
273 273
 			var target = findChildObject(this, 'target');
274
-                        submit.clicked.connect(function() {
275
-                            var proceed = false;
276
-                            if(validateCapacity()) {
277
-                                proceed = true;
278
-                            } else {
279
-                                proceed = displayWarning(TTR("sampleRoastingBatch", "Suspicious Input"),
274
+            submit.clicked.connect(function() {
275
+                var proceed = false;
276
+                if(validateCapacity()) {
277
+                    proceed = true;
278
+                } else {
279
+                    proceed = displayWarning(TTR("sampleRoastingBatch", "Suspicious Input"),
280 280
                                 TTR("sampleRoastingBatch", "Entered green coffee weight exceeds maximum batch size. Continue?"));
281
-                            }
282
-                            if(proceed) {
283
-                                doSubmit();
284
-                            }
285
-                        });
286
-                        var doSubmit = function() {
281
+                }
282
+                if(proceed) {
283
+                    doSubmit();
284
+                }
285
+            });
286
+            var doSubmit = function() {
287 287
 				query = new QSqlQuery();
288 288
 				query.prepare("INSERT INTO files (id, name, type, note, file) VALUES(DEFAULT, :name, 'profile', NULL, :data) RETURNING id");
289 289
 				query.bind(":name", timefield.text + " " + name.text + " " + profileName.currentText);

+ 786
- 809
config/Windows/productionroaster.xml
文件差異過大導致無法顯示
查看文件


+ 1
- 0
config/config.xml 查看文件

@@ -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();

+ 4
- 2
src/Typica.pro 查看文件

@@ -26,7 +26,8 @@ HEADERS += moc_typica.cpp \
26 26
     scale.h \
27 27
     draglabel.h \
28 28
     daterangeselector.h \
29
-    licensewindow.h
29
+    licensewindow.h \
30
+    printerselector.h
30 31
 SOURCES += typica.cpp \
31 32
     helpmenu.cpp \
32 33
     abouttypica.cpp \
@@ -36,7 +37,8 @@ SOURCES += typica.cpp \
36 37
     scale.cpp \
37 38
     draglabel.cpp \
38 39
     daterangeselector.cpp \
39
-    licensewindow.cpp
40
+    licensewindow.cpp \
41
+    printerselector.cpp
40 42
 
41 43
 RESOURCES += \
42 44
     resources.qrc

+ 5
- 5
src/abouttypica.cpp 查看文件

@@ -1,9 +1,9 @@
1
-/*275:*/
1
+/*287:*/
2 2
 #line 33 "./abouttypica.w"
3 3
 
4 4
 #include "abouttypica.h"
5 5
 
6
-/*276:*/
6
+/*288:*/
7 7
 #line 42 "./abouttypica.w"
8 8
 
9 9
 AboutTypica::AboutTypica():QMainWindow(NULL)
@@ -17,10 +17,10 @@ aboutFile.close();
17 17
 setCentralWidget(banner);
18 18
 }
19 19
 
20
-#line 6595 "./typica.w"
20
+#line 6769 "./typica.w"
21 21
 
22
-/*:276*/
22
+/*:288*/
23 23
 #line 36 "./abouttypica.w"
24 24
 
25 25
 
26
-/*:275*/
26
+/*:287*/

+ 2
- 2
src/abouttypica.h 查看文件

@@ -1,4 +1,4 @@
1
-/*274:*/
1
+/*286:*/
2 2
 #line 14 "./abouttypica.w"
3 3
 
4 4
 #include <QMainWindow> 
@@ -17,4 +17,4 @@ AboutTypica();
17 17
 
18 18
 #endif
19 19
 
20
-/*:274*/
20
+/*:286*/

+ 19
- 19
src/daterangeselector.cpp 查看文件

@@ -1,4 +1,4 @@
1
-/*666:*/
1
+/*694:*/
2 2
 #line 70 "./daterangeselector.w"
3 3
 
4 4
 #include <QCalendarWidget> 
@@ -11,7 +11,7 @@
11 11
 
12 12
 #include "daterangeselector.h"
13 13
 
14
-/*668:*/
14
+/*696:*/
15 15
 #line 117 "./daterangeselector.w"
16 16
 
17 17
 CustomDateRangePopup::CustomDateRangePopup(QWidget*parent):
@@ -55,7 +55,7 @@ outerLayout->addLayout(buttonLayout);
55 55
 setLayout(outerLayout);
56 56
 }
57 57
 
58
-/*:668*//*669:*/
58
+/*:696*//*697:*/
59 59
 #line 165 "./daterangeselector.w"
60 60
 
61 61
 void CustomDateRangePopup::hideEvent(QHideEvent*)
@@ -63,7 +63,7 @@ void CustomDateRangePopup::hideEvent(QHideEvent*)
63 63
 emit hidingPopup();
64 64
 }
65 65
 
66
-/*:669*//*670:*/
66
+/*:697*//*698:*/
67 67
 #line 174 "./daterangeselector.w"
68 68
 
69 69
 void CustomDateRangePopup::applyRange()
@@ -78,7 +78,7 @@ endDateSelector->selectedDate().toString(Qt::ISODate)));
78 78
 hide();
79 79
 }
80 80
 
81
-/*:670*//*671:*/
81
+/*:698*//*699:*/
82 82
 #line 191 "./daterangeselector.w"
83 83
 
84 84
 void CustomDateRangePopup::validateRange()
@@ -93,10 +93,10 @@ applyButton->setEnabled(true);
93 93
 }
94 94
 }
95 95
 
96
-/*:671*/
96
+/*:699*/
97 97
 #line 81 "./daterangeselector.w"
98 98
 
99
-/*672:*/
99
+/*700:*/
100 100
 #line 209 "./daterangeselector.w"
101 101
 
102 102
 DateRangeSelector::DateRangeSelector(QWidget*parent):
@@ -108,7 +108,7 @@ connect(quickSelector,SIGNAL(currentIndexChanged(int)),this,SLOT(updateRange(int
108 108
 QDate currentDate= QDate::currentDate();
109 109
 
110 110
 QHBoxLayout*layout= new QHBoxLayout;
111
-/*673:*/
111
+/*701:*/
112 112
 #line 238 "./daterangeselector.w"
113 113
 
114 114
 quickSelector->addItem("Yesterday",QVariant(QStringList()<<
@@ -188,7 +188,7 @@ quickSelector->insertSeparator(quickSelector->count());
188 188
 quickSelector->addItem("Lifetime");
189 189
 quickSelector->addItem("Custom");
190 190
 
191
-/*:673*/
191
+/*:701*/
192 192
 #line 219 "./daterangeselector.w"
193 193
 
194 194
 QToolButton*customButton= new QToolButton;
@@ -201,7 +201,7 @@ setLayout(layout);
201 201
 connect(customButton,SIGNAL(clicked()),this,SLOT(toggleCustom()));
202 202
 }
203 203
 
204
-/*:672*//*674:*/
204
+/*:700*//*702:*/
205 205
 #line 321 "./daterangeselector.w"
206 206
 
207 207
 void DateRangeSelector::updateRange(int index)
@@ -217,7 +217,7 @@ emit rangeUpdated(quickSelector->itemData(quickSelector->currentIndex()));
217 217
 }
218 218
 }
219 219
 
220
-/*:674*//*675:*/
220
+/*:702*//*703:*/
221 221
 #line 338 "./daterangeselector.w"
222 222
 
223 223
 void DateRangeSelector::popupHidden()
@@ -227,7 +227,7 @@ customRangeSelector= NULL;
227 227
 quickSelector->setCurrentIndex(lastIndex);
228 228
 }
229 229
 
230
-/*:675*//*676:*/
230
+/*:703*//*704:*/
231 231
 #line 349 "./daterangeselector.w"
232 232
 
233 233
 void DateRangeSelector::setCustomRange(QVariant range)
@@ -238,7 +238,7 @@ lastIndex= quickSelector->count()-1;
238 238
 quickSelector->setCurrentIndex(lastIndex);
239 239
 }
240 240
 
241
-/*:676*//*677:*/
241
+/*:704*//*705:*/
242 242
 #line 364 "./daterangeselector.w"
243 243
 
244 244
 void DateRangeSelector::toggleCustom()
@@ -279,7 +279,7 @@ customRangeSelector= NULL;
279 279
 }
280 280
 }
281 281
 
282
-/*:677*//*678:*/
282
+/*:705*//*706:*/
283 283
 #line 406 "./daterangeselector.w"
284 284
 
285 285
 QVariant DateRangeSelector::currentRange()
@@ -287,7 +287,7 @@ QVariant DateRangeSelector::currentRange()
287 287
 return quickSelector->itemData(lastIndex);
288 288
 }
289 289
 
290
-/*:678*//*679:*/
290
+/*:706*//*707:*/
291 291
 #line 414 "./daterangeselector.w"
292 292
 
293 293
 void DateRangeSelector::setCurrentIndex(int index)
@@ -300,7 +300,7 @@ int DateRangeSelector::currentIndex()
300 300
 return quickSelector->currentIndex();
301 301
 }
302 302
 
303
-/*:679*//*680:*/
303
+/*:707*//*708:*/
304 304
 #line 434 "./daterangeselector.w"
305 305
 
306 306
 void DateRangeSelector::setLifetimeRange(QString startDate,QString endDate)
@@ -309,7 +309,7 @@ quickSelector->setItemData(quickSelector->count()-2,
309 309
 QVariant(QStringList()<<startDate<<endDate));
310 310
 }
311 311
 
312
-/*:680*//*681:*/
312
+/*:708*//*709:*/
313 313
 #line 444 "./daterangeselector.w"
314 314
 
315 315
 void DateRangeSelector::removeIndex(int index)
@@ -317,7 +317,7 @@ void DateRangeSelector::removeIndex(int index)
317 317
 quickSelector->removeItem(index);
318 318
 }
319 319
 
320
-/*:681*/
320
+/*:709*/
321 321
 #line 82 "./daterangeselector.w"
322 322
 
323 323
 
@@ -327,4 +327,4 @@ quickSelector->removeItem(index);
327 327
 #endif
328 328
 #endif
329 329
 
330
-/*:666*/
330
+/*:694*/

+ 4
- 4
src/daterangeselector.h 查看文件

@@ -1,4 +1,4 @@
1
-/*665:*/
1
+/*693:*/
2 2
 #line 30 "./daterangeselector.w"
3 3
 
4 4
 
@@ -9,7 +9,7 @@
9 9
 #ifndef TypicaDateRangeSelectorHeader
10 10
 #define TypicaDateRangeSelectorHeader
11 11
 
12
-/*667:*/
12
+/*695:*/
13 13
 #line 93 "./daterangeselector.w"
14 14
 
15 15
 class CustomDateRangePopup:public QWidget
@@ -31,7 +31,7 @@ QCalendarWidget*endDateSelector;
31 31
 QPushButton*applyButton;
32 32
 };
33 33
 
34
-/*:667*/
34
+/*:695*/
35 35
 #line 39 "./daterangeselector.w"
36 36
 
37 37
 
@@ -62,4 +62,4 @@ int lastIndex;
62 62
 
63 63
 #endif
64 64
 
65
-/*:665*/
65
+/*:693*/

+ 2
- 2
src/draglabel.cpp 查看文件

@@ -1,4 +1,4 @@
1
-/*1014:*/
1
+/*1039:*/
2 2
 #line 33 "./scales.w"
3 3
 
4 4
 #include "draglabel.h"
@@ -26,4 +26,4 @@ drag->exec();
26 26
 }
27 27
 }
28 28
 
29
-/*:1014*/
29
+/*:1039*/

+ 2
- 2
src/draglabel.h 查看文件

@@ -1,4 +1,4 @@
1
-/*1013:*/
1
+/*1038:*/
2 2
 #line 13 "./scales.w"
3 3
 
4 4
 #ifndef TypicaDragLabelInclude
@@ -17,4 +17,4 @@ void mousePressEvent(QMouseEvent*event);
17 17
 
18 18
 #endif
19 19
 
20
-/*:1013*/
20
+/*:1038*/

+ 5
- 1
src/graphsettings.w 查看文件

@@ -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);

+ 7
- 7
src/helpmenu.cpp 查看文件

@@ -1,11 +1,11 @@
1
-/*204:*/
1
+/*207:*/
2 2
 #line 36 "./helpmenu.w"
3 3
 
4 4
 #include "helpmenu.h"
5 5
 #include "abouttypica.h"
6 6
 #include "licensewindow.h"
7 7
 
8
-/*205:*/
8
+/*208:*/
9 9
 #line 46 "./helpmenu.w"
10 10
 
11 11
 HelpMenu::HelpMenu():QMenu()
@@ -24,7 +24,7 @@ connect(licenseAction,SIGNAL(triggered()),this,SLOT(displayLicenseWindow()));
24 24
 #endif
25 25
 }
26 26
 
27
-/*:205*//*206:*/
27
+/*:208*//*209:*/
28 28
 #line 66 "./helpmenu.w"
29 29
 
30 30
 void HelpMenu::displayAboutTypica()
@@ -33,7 +33,7 @@ AboutTypica*aboutBox= new AboutTypica;
33 33
 aboutBox->show();
34 34
 }
35 35
 
36
-/*:206*//*207:*/
36
+/*:209*//*210:*/
37 37
 #line 76 "./helpmenu.w"
38 38
 
39 39
 void HelpMenu::displayLicenseWindow()
@@ -42,11 +42,11 @@ LicenseWindow*window= new LicenseWindow;
42 42
 window->show();
43 43
 }
44 44
 
45
-#line 4775 "./typica.w"
45
+#line 4826 "./typica.w"
46 46
 
47 47
 #line 1 "./licensewindow.w"
48
-/*:207*/
48
+/*:210*/
49 49
 #line 41 "./helpmenu.w"
50 50
 
51 51
 
52
-/*:204*/
52
+/*:207*/

+ 2
- 2
src/helpmenu.h 查看文件

@@ -1,4 +1,4 @@
1
-/*203:*/
1
+/*206:*/
2 2
 #line 16 "./helpmenu.w"
3 3
 
4 4
 #include <QMenu> 
@@ -18,4 +18,4 @@ void displayLicenseWindow();
18 18
 
19 19
 #endif
20 20
 
21
-/*:203*/
21
+/*:206*/

+ 10
- 10
src/licensewindow.cpp 查看文件

@@ -1,7 +1,7 @@
1
-/*209:*/
1
+/*212:*/
2 2
 #line 36 "./licensewindow.w"
3 3
 
4
-/*213:*/
4
+/*216:*/
5 5
 #line 97 "./licensewindow.w"
6 6
 
7 7
 #include "licensewindow.h"
@@ -11,12 +11,12 @@
11 11
 #include <QVariant> 
12 12
 #include <QUrl> 
13 13
 
14
-#line 4777 "./typica.w"
14
+#line 4828 "./typica.w"
15 15
 
16
-/*:213*/
16
+/*:216*/
17 17
 #line 37 "./licensewindow.w"
18 18
 
19
-/*210:*/
19
+/*213:*/
20 20
 #line 43 "./licensewindow.w"
21 21
 
22 22
 LicenseWindow::LicenseWindow()
@@ -25,7 +25,7 @@ LicenseWindow::LicenseWindow()
25 25
 QSplitter*split= new QSplitter;
26 26
 QListWidget*projects= new QListWidget;
27 27
 
28
-/*212:*/
28
+/*215:*/
29 29
 #line 79 "./licensewindow.w"
30 30
 
31 31
 QListWidgetItem*item= new QListWidgetItem("Typica",projects);
@@ -43,7 +43,7 @@ item->setData(Qt::UserRole,QVariant(QUrl("qrc:/resources/html/licenses/qextseria
43 43
 item= new QListWidgetItem("Qt",projects);
44 44
 item->setData(Qt::UserRole,QVariant(QUrl("qrc:/resources/html/licenses/qt.html")));
45 45
 
46
-/*:212*/
46
+/*:215*/
47 47
 #line 50 "./licensewindow.w"
48 48
 
49 49
 connect(projects,SIGNAL(currentItemChanged(QListWidgetItem*,QListWidgetItem*)),
@@ -54,7 +54,7 @@ split->addWidget(view);
54 54
 setCentralWidget(split);
55 55
 }
56 56
 
57
-/*:210*//*211:*/
57
+/*:213*//*214:*/
58 58
 #line 64 "./licensewindow.w"
59 59
 
60 60
 void LicenseWindow::setWebView(QListWidgetItem*current,QListWidgetItem*)
@@ -62,8 +62,8 @@ void LicenseWindow::setWebView(QListWidgetItem*current,QListWidgetItem*)
62 62
 view->load(current->data(Qt::UserRole).toUrl());
63 63
 }
64 64
 
65
-/*:211*/
65
+/*:214*/
66 66
 #line 38 "./licensewindow.w"
67 67
 
68 68
 
69
-/*:209*/
69
+/*:212*/

+ 2
- 2
src/licensewindow.h 查看文件

@@ -1,4 +1,4 @@
1
-/*208:*/
1
+/*211:*/
2 2
 #line 13 "./licensewindow.w"
3 3
 
4 4
 #include <QMainWindow> 
@@ -21,4 +21,4 @@ QWebView*view;
21 21
 
22 22
 #endif
23 23
 
24
-/*:208*/
24
+/*:211*/

+ 103
- 21
src/moc_typica.cpp 查看文件

@@ -3096,6 +3096,80 @@ int ReportTable::qt_metacall(QMetaObject::Call _c, int _id, void **_a)
3096 3096
     }
3097 3097
     return _id;
3098 3098
 }
3099
+static const uint qt_meta_data_PluginAction[] = {
3100
+
3101
+ // content:
3102
+       6,       // revision
3103
+       0,       // classname
3104
+       0,    0, // classinfo
3105
+       1,   14, // methods
3106
+       0,    0, // properties
3107
+       0,    0, // enums/sets
3108
+       0,    0, // constructors
3109
+       0,       // flags
3110
+       0,       // signalCount
3111
+
3112
+ // slots: signature, parameters, type, tag, flags
3113
+      14,   13,   13,   13, 0x08,
3114
+
3115
+       0        // eod
3116
+};
3117
+
3118
+static const char qt_meta_stringdata_PluginAction[] = {
3119
+    "PluginAction\0\0runScript()\0"
3120
+};
3121
+
3122
+void PluginAction::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
3123
+{
3124
+    if (_c == QMetaObject::InvokeMetaMethod) {
3125
+        Q_ASSERT(staticMetaObject.cast(_o));
3126
+        PluginAction *_t = static_cast<PluginAction *>(_o);
3127
+        switch (_id) {
3128
+        case 0: _t->runScript(); break;
3129
+        default: ;
3130
+        }
3131
+    }
3132
+    Q_UNUSED(_a);
3133
+}
3134
+
3135
+const QMetaObjectExtraData PluginAction::staticMetaObjectExtraData = {
3136
+    0,  qt_static_metacall 
3137
+};
3138
+
3139
+const QMetaObject PluginAction::staticMetaObject = {
3140
+    { &QAction::staticMetaObject, qt_meta_stringdata_PluginAction,
3141
+      qt_meta_data_PluginAction, &staticMetaObjectExtraData }
3142
+};
3143
+
3144
+#ifdef Q_NO_DATA_RELOCATION
3145
+const QMetaObject &PluginAction::getStaticMetaObject() { return staticMetaObject; }
3146
+#endif //Q_NO_DATA_RELOCATION
3147
+
3148
+const QMetaObject *PluginAction::metaObject() const
3149
+{
3150
+    return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject;
3151
+}
3152
+
3153
+void *PluginAction::qt_metacast(const char *_clname)
3154
+{
3155
+    if (!_clname) return 0;
3156
+    if (!strcmp(_clname, qt_meta_stringdata_PluginAction))
3157
+        return static_cast<void*>(const_cast< PluginAction*>(this));
3158
+    return QAction::qt_metacast(_clname);
3159
+}
3160
+
3161
+int PluginAction::qt_metacall(QMetaObject::Call _c, int _id, void **_a)
3162
+{
3163
+    _id = QAction::qt_metacall(_c, _id, _a);
3164
+    if (_id < 0)
3165
+        return _id;
3166
+    if (_c == QMetaObject::InvokeMetaMethod) {
3167
+        if (_id < 1)
3168
+            qt_static_metacall(this, _c, _id, _a);
3169
+        _id -= 1;
3170
+    }
3171
+    return _id;
3172
+}
3099 3173
 static const uint qt_meta_data_FormArray[] = {
3100 3174
 
3101 3175
  // content:
@@ -6283,7 +6357,7 @@ static const uint qt_meta_data_ModbusNG[] = {
6283 6357
        6,       // revision
6284 6358
        0,       // classname
6285 6359
        0,    0, // classinfo
6286
-       7,   14, // methods
6360
+       9,   14, // methods
6287 6361
        0,    0, // properties
6288 6362
        0,    0, // enums/sets
6289 6363
        0,    0, // constructors
@@ -6294,22 +6368,24 @@ static const uint qt_meta_data_ModbusNG[] = {
6294 6368
       10,    9,    9,    9, 0x08,
6295 6369
       28,    9,    9,    9, 0x08,
6296 6370
       38,    9,    9,    9, 0x08,
6371
+      54,    9,    9,    9, 0x08,
6297 6372
 
6298 6373
  // methods: signature, parameters, type, tag, flags
6299
-      58,    9,   54,    9, 0x02,
6300
-      81,    9,   73,    9, 0x02,
6301
-     104,    9,   73,    9, 0x02,
6302
-     135,    9,  130,    9, 0x02,
6374
+      77,    9,   73,    9, 0x02,
6375
+     100,    9,   92,    9, 0x02,
6376
+     123,    9,   92,    9, 0x02,
6377
+     154,    9,  149,    9, 0x02,
6378
+     175,    9,   92,    9, 0x02,
6303 6379
 
6304 6380
        0        // eod
6305 6381
 };
6306 6382
 
6307 6383
 static const char qt_meta_stringdata_ModbusNG[] = {
6308 6384
     "ModbusNG\0\0sendNextMessage()\0timeout()\0"
6309
-    "dataAvailable()\0int\0channelCount()\0"
6310
-    "QString\0channelColumnName(int)\0"
6311
-    "channelIndicatorText(int)\0bool\0"
6312
-    "isChannelHidden(int)\0"
6385
+    "dataAvailable()\0rateLimitTimeout()\0"
6386
+    "int\0channelCount()\0QString\0"
6387
+    "channelColumnName(int)\0channelIndicatorText(int)\0"
6388
+    "bool\0isChannelHidden(int)\0channelType(int)\0"
6313 6389
 };
6314 6390
 
6315 6391
 void ModbusNG::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
@@ -6321,14 +6397,17 @@ void ModbusNG::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, vo
6321 6397
         case 0: _t->sendNextMessage(); break;
6322 6398
         case 1: _t->timeout(); break;
6323 6399
         case 2: _t->dataAvailable(); break;
6324
-        case 3: { int _r = _t->channelCount();
6400
+        case 3: _t->rateLimitTimeout(); break;
6401
+        case 4: { int _r = _t->channelCount();
6325 6402
             if (_a[0]) *reinterpret_cast< int*>(_a[0]) = _r; }  break;
6326
-        case 4: { QString _r = _t->channelColumnName((*reinterpret_cast< int(*)>(_a[1])));
6403
+        case 5: { QString _r = _t->channelColumnName((*reinterpret_cast< int(*)>(_a[1])));
6327 6404
             if (_a[0]) *reinterpret_cast< QString*>(_a[0]) = _r; }  break;
6328
-        case 5: { QString _r = _t->channelIndicatorText((*reinterpret_cast< int(*)>(_a[1])));
6405
+        case 6: { QString _r = _t->channelIndicatorText((*reinterpret_cast< int(*)>(_a[1])));
6329 6406
             if (_a[0]) *reinterpret_cast< QString*>(_a[0]) = _r; }  break;
6330
-        case 6: { bool _r = _t->isChannelHidden((*reinterpret_cast< int(*)>(_a[1])));
6407
+        case 7: { bool _r = _t->isChannelHidden((*reinterpret_cast< int(*)>(_a[1])));
6331 6408
             if (_a[0]) *reinterpret_cast< bool*>(_a[0]) = _r; }  break;
6409
+        case 8: { QString _r = _t->channelType((*reinterpret_cast< int(*)>(_a[1])));
6410
+            if (_a[0]) *reinterpret_cast< QString*>(_a[0]) = _r; }  break;
6332 6411
         default: ;
6333 6412
         }
6334 6413
     }
@@ -6366,9 +6445,9 @@ int ModbusNG::qt_metacall(QMetaObject::Call _c, int _id, void **_a)
6366 6445
     if (_id < 0)
6367 6446
         return _id;
6368 6447
     if (_c == QMetaObject::InvokeMetaMethod) {
6369
-        if (_id < 7)
6448
+        if (_id < 9)
6370 6449
             qt_static_metacall(this, _c, _id, _a);
6371
-        _id -= 7;
6450
+        _id -= 9;
6372 6451
     }
6373 6452
     return _id;
6374 6453
 }
@@ -7350,19 +7429,20 @@ static const uint qt_meta_data_TranslationConfWidget[] = {
7350 7429
        6,       // revision
7351 7430
        0,       // classname
7352 7431
        0,    0, // classinfo
7353
-       2,   14, // methods
7432
+       3,   14, // methods
7354 7433
        0,    0, // properties
7355 7434
        0,    0, // enums/sets
7356
-       1,   24, // constructors
7435
+       1,   29, // constructors
7357 7436
        0,       // flags
7358 7437
        0,       // signalCount
7359 7438
 
7360 7439
  // slots: signature, parameters, type, tag, flags
7361 7440
       30,   23,   22,   22, 0x08,
7362 7441
       60,   22,   22,   22, 0x08,
7442
+      80,   22,   22,   22, 0x08,
7363 7443
 
7364 7444
  // constructors: signature, parameters, type, tag, flags
7365
-      92,   80,   22,   22, 0x0e,
7445
+     106,   94,   22,   22, 0x0e,
7366 7446
 
7367 7447
        0        // eod
7368 7448
 };
@@ -7370,7 +7450,8 @@ static const uint qt_meta_data_TranslationConfWidget[] = {
7370 7450
 static const char qt_meta_stringdata_TranslationConfWidget[] = {
7371 7451
     "TranslationConfWidget\0\0column\0"
7372 7452
     "updateMatchingColumn(QString)\0"
7373
-    "updateTemperature()\0model,index\0"
7453
+    "updateTemperature()\0updateDelay()\0"
7454
+    "model,index\0"
7374 7455
     "TranslationConfWidget(DeviceTreeModel*,QModelIndex)\0"
7375 7456
 };
7376 7457
 
@@ -7387,6 +7468,7 @@ void TranslationConfWidget::qt_static_metacall(QObject *_o, QMetaObject::Call _c
7387 7468
         switch (_id) {
7388 7469
         case 0: _t->updateMatchingColumn((*reinterpret_cast< const QString(*)>(_a[1]))); break;
7389 7470
         case 1: _t->updateTemperature(); break;
7471
+        case 2: _t->updateDelay(); break;
7390 7472
         default: ;
7391 7473
         }
7392 7474
     }
@@ -7424,9 +7506,9 @@ int TranslationConfWidget::qt_metacall(QMetaObject::Call _c, int _id, void **_a)
7424 7506
     if (_id < 0)
7425 7507
         return _id;
7426 7508
     if (_c == QMetaObject::InvokeMetaMethod) {
7427
-        if (_id < 2)
7509
+        if (_id < 3)
7428 7510
             qt_static_metacall(this, _c, _id, _a);
7429
-        _id -= 2;
7511
+        _id -= 3;
7430 7512
     }
7431 7513
     return _id;
7432 7514
 }

+ 162
- 0
src/plugins.w 查看文件

@@ -0,0 +1,162 @@
1
+@** Simple Plugins.
2
+
3
+\noindent The original motivation for this feature is to provide a simple way
4
+to allow importing data from other data logging applications. The problem is
5
+that there are huge differences in the data formats exported by different
6
+applications, sometimes there are differences that depend on how the other
7
+application was configured which cannot be reliably determined in an automated
8
+fashion, and if a substantial number of import plugins were created, any given
9
+person using Typica would be unlikely to ever use most of them.
10
+
11
+Based on these concerns, I wanted something that would make it easy to create
12
+new import plugins without the need to create a new build of Typica every time,
13
+I wanted it to be relatively easy for people to modify example import plugins
14
+to suit the data they wanted to import, and I wanted it to be easy for people
15
+to hide plugins that they were not required.
16
+
17
+This is handled in a way similar to reports. A new directory is provided with
18
+the \pn{} configuration which contains files with script code. A menu item is
19
+available that will examine the files in that folder to populate its sub-menu.
20
+
21
+@<Process plugin item@>=
22
+QMenu *pluginMenu = new QMenu(menu);
23
+if(itemElement.hasAttribute("id"))
24
+{
25
+	pluginMenu->setObjectName(itemElement.attribute("id"));
26
+}
27
+if(itemElement.hasAttribute("title"))
28
+{
29
+	pluginMenu->setTitle(itemElement.attribute("title"));
30
+}
31
+if(itemElement.hasAttribute("src"))
32
+{
33
+	QSettings settings;
34
+	QString pluginDirectory = QString("%1/%2").
35
+		arg(settings.value("config").toString()).
36
+		arg(itemElement.attribute("src"));
37
+	QDir directory(pluginDirectory);
38
+	directory.setFilter(QDir::Files);
39
+	directory.setSorting(QDir::Name);
40
+	QStringList nameFilter;
41
+	nameFilter << "*.js";
42
+	directory.setNameFilters(nameFilter);
43
+	QFileInfoList pluginFiles = directory.entryInfoList();
44
+	for(int k = 0; k < pluginFiles.size(); k++)
45
+	{
46
+		PluginAction *pa = new PluginAction(pluginFiles.at(k), pluginMenu);
47
+		if(itemElement.hasAttribute("preRun"))
48
+		{
49
+			pa->setPreRun(itemElement.attribute("preRun"));
50
+		}
51
+		if(itemElement.hasAttribute("postRun"))
52
+		{
53
+			pa->setPostRun(itemElement.attribute("postRun"));
54
+		}
55
+		pluginMenu->addAction(pa);
56
+	}
57
+}
58
+menu->addMenu(pluginMenu);
59
+
60
+@ The sub-menu items are a subclass of |QAction| which holds all of the
61
+information needed to respond to its activation.
62
+
63
+@<Class declarations@>=
64
+class PluginAction : public QAction
65
+{
66
+	Q_OBJECT
67
+	Q_PROPERTY(QString preRun READ preRun WRITE setPreRun);
68
+	Q_PROPERTY(QString postRun READ postRun WRITE setPostRun);
69
+	public:
70
+		PluginAction(const QFileInfo &info, QObject *parent);
71
+		QString preRun();
72
+		QString postRun();
73
+	public slots:
74
+		void setPreRun(const QString &script);
75
+		void setPostRun(const QString &script);
76
+	private slots:
77
+		void runScript();
78
+	private:
79
+		QString pluginFile;
80
+		QString preRunScript;
81
+		QString postRunScript;
82
+};
83
+
84
+@ The constructor takes a |QFileInfo| and uses that to extract the path of the
85
+file used to respond to the action activation as well as the text that should
86
+be used in the menu text. It also takes a |QObject*| parent which should be the
87
+|QMenu| the |PluginAction| will be placed in.
88
+
89
+Everything interesting happens in |runScript()| which is called when the action
90
+is triggered.
91
+
92
+@<PluginAction implementation@>=
93
+PluginAction::PluginAction(const QFileInfo &info, QObject *parent) :
94
+	QAction(parent), preRunScript(""), postRunScript("")
95
+{
96
+	pluginFile = info.absoluteFilePath();
97
+	setText(info.baseName());
98
+	connect(this, SIGNAL(triggered()), this, SLOT(runScript()));
99
+}
100
+
101
+void PluginAction::runScript()
102
+{
103
+	QFile file(pluginFile);
104
+	if(file.open(QIODevice::ReadOnly))
105
+	{
106
+		QScriptEngine *engine = AppInstance->engine;
107
+		QScriptContext *context = engine->pushContext();
108
+		if(parent()->dynamicPropertyNames().contains("activationObject"))
109
+		{
110
+			QScriptValue activationObject =
111
+				parent()->property("activationObject").value<QScriptValue>();
112
+			context->setActivationObject(activationObject);
113
+		}
114
+		QString script(file.readAll());
115
+		QScriptValue retval = engine->evaluate(preRunScript + script + postRunScript, pluginFile);
116
+		if(engine->hasUncaughtException())
117
+		{
118
+			qDebug() << "Uncaught exception: " <<
119
+				engine->uncaughtException().toString() <<
120
+				" in " << pluginFile << " line: " <<
121
+				engine->uncaughtExceptionLineNumber();
122
+		}
123
+		engine->popContext();
124
+		file.close();
125
+	}
126
+}
127
+
128
+@ Pre-run and post-run scripts can be set to handle boilerplate that would
129
+otherwise need to be included in all plugins.
130
+
131
+@<PluginAction implementation@>=
132
+QString PluginAction::preRun()
133
+{
134
+	return preRunScript;
135
+}
136
+
137
+QString PluginAction::postRun()
138
+{
139
+	return postRunScript;
140
+}
141
+
142
+void PluginAction::setPreRun(const QString &script)
143
+{
144
+	preRunScript = script;
145
+}
146
+
147
+void PluginAction::setPostRun(const QString &script)
148
+{
149
+	postRunScript = script;
150
+}
151
+
152
+@ In order to get the activation object in this way, we need to allow a
153
+|QScriptValue| to be stored in a |QVariant|.
154
+
155
+@<Class declarations@>=
156
+Q_DECLARE_METATYPE(QScriptValue)
157
+
158
+@ This is added to the list of class implementations.
159
+
160
+@<Class implementations@>=
161
+@<PluginAction implementation@>
162
+

+ 22
- 0
src/printerselector.cpp 查看文件

@@ -0,0 +1,22 @@
1
+/*590:*/
2
+#line 45 "./printerselector.w"
3
+
4
+#include "printerselector.h"
5
+
6
+/*591:*/
7
+#line 53 "./printerselector.w"
8
+
9
+PrinterSelector::PrinterSelector():QComboBox(NULL)
10
+{
11
+QList<QPrinterInfo> printers= QPrinterInfo::availablePrinters();
12
+foreach(QPrinterInfo info,printers)
13
+{
14
+addItem(info.printerName());
15
+}
16
+}
17
+
18
+/*:591*/
19
+#line 48 "./printerselector.w"
20
+
21
+
22
+/*:590*/

+ 19
- 0
src/printerselector.h 查看文件

@@ -0,0 +1,19 @@
1
+/*588:*/
2
+#line 22 "./printerselector.w"
3
+
4
+#include <QPrinterInfo> 
5
+#include <QComboBox> 
6
+
7
+#ifndef TypicaPrinterSelectorHeader
8
+#define TypicaPrinterSelectorHeader
9
+
10
+class PrinterSelector:public QComboBox
11
+{
12
+Q_OBJECT
13
+public:
14
+PrinterSelector();
15
+};
16
+
17
+#endif
18
+
19
+/*:588*/

+ 116
- 0
src/printerselector.w 查看文件

@@ -0,0 +1,116 @@
1
+@* Saved Printers.
2
+
3
+\noindent In most cases it's best to handle printing in a way that is common
4
+across many applications. Put a Print menu option in a File menu, bring up the
5
+platform's standard print dialog, and allow people to take full advantage of
6
+the flexibility this provides.
7
+
8
+In more specialized use cases, however, it may make more sense to provide
9
+faster access to a printer that might not be the default printer for that
10
+computer. The first use in Typica where this makes sense is in printing tags
11
+that can follow the coffee and uniquely identify that batch. Using a full sheet
12
+of paper for this might be excessive and time consuming. Instead, it might make
13
+sense to get a small, inexpensive thermal receipt printer to keep at the
14
+roaster. If this were not the default printer, it would quickly become tedious
15
+to bring up the print dialog and change the selected printer after every batch.
16
+
17
+In cases like this, it would be better to provide a combo box in the window
18
+where a printer can be selected and remembered as the default printer just for
19
+that particular use, and allowing people to print directly to that printer
20
+without going through extra steps.
21
+
22
+@(printerselector.h@>=
23
+#include <QPrinterInfo>
24
+#include <QComboBox>
25
+
26
+#ifndef TypicaPrinterSelectorHeader
27
+#define TypicaPrinterSelectorHeader
28
+
29
+class PrinterSelector : public QComboBox@/
30
+{
31
+	@[Q_OBJECT@]@;
32
+	public:
33
+		PrinterSelector();
34
+};
35
+
36
+#endif
37
+
38
+@ The main file also requires this header.
39
+
40
+@<Header files to include@>=
41
+#include "printerselector.h"
42
+
43
+@ Implementation of this class is in a separate file.
44
+
45
+@(printerselector.cpp@>=
46
+#include "printerselector.h"
47
+
48
+@<PrinterSelector implementation@>@;
49
+
50
+@ The constructor looks at the list of available printers and populates itself
51
+with these.
52
+
53
+@<PrinterSelector implementation@>=
54
+PrinterSelector::PrinterSelector() : QComboBox(NULL)
55
+{
56
+	QList<QPrinterInfo> printers = QPrinterInfo::availablePrinters();
57
+	foreach(QPrinterInfo info, printers)
58
+	{
59
+		addItem(info.printerName());
60
+	}
61
+}
62
+
63
+@ The host environment is informed of this class in the usual way starting with
64
+a constructor function prototype. Another prototype is also needed for adding
65
+this to a layout from XML.
66
+
67
+@<Function prototypes for scripting@>=
68
+QScriptValue constructPrinterSelector(QScriptContext *context,
69
+                                      QScriptEngine *engine);
70
+void addPrinterSelectorToLayout(QDomElement element,
71
+                                QStack<QWidget *> *widgetStack,
72
+                                QStack<QLayout *> *layoutStack);
73
+
74
+@ The engine is informed of this function.
75
+
76
+@<Set up the scripting engine@>=
77
+constructor = engine->newFunction(constructPrinterSelector);
78
+engine->globalObject().setProperty("PrinterSelector", constructor);
79
+
80
+@ There is nothing special about the constructor. If there were additional
81
+properties needed beyond those supplied by |setQComboBoxProperties()| it would
82
+make sense to add another function to the chain for setting script value
83
+properties.
84
+
85
+@<Functions for scripting@>=
86
+QScriptValue constructPrinterSelector(QScriptContext *, QScriptEngine *engine)
87
+{
88
+	QScriptValue object = engine->newQObject(new PrinterSelector);
89
+	setQComboBoxProperties(object, engine);
90
+	return object;
91
+}
92
+
93
+@ It should also be possible to add this to a layout from the XML portion of
94
+the configuration document.
95
+
96
+@<Functions for scripting@>=
97
+void addPrinterSelectorToLayout(QDomElement element, QStack<QWidget *> *,
98
+                                QStack<QLayout *> *layoutStack)
99
+{
100
+	PrinterSelector *selector = new PrinterSelector;
101
+	if(element.hasAttribute("id"))
102
+	{
103
+		selector->setObjectName(element.attribute("id"));
104
+	}
105
+	QBoxLayout *layout = qobject_cast<QBoxLayout *>(layoutStack->top());
106
+	layout->addWidget(selector);
107
+}
108
+
109
+@ This is added in the usual way.
110
+
111
+@<Additional box layout elements@>=
112
+else if(currentElement.tagName() == "printerselector")
113
+{
114
+	addPrinterSelectorToLayout(currentElement, widgetStack, layoutStack);
115
+}
116
+

+ 6
- 6
src/scale.cpp 查看文件

@@ -1,4 +1,4 @@
1
-/*1020:*/
1
+/*1045:*/
2 2
 #line 135 "./scales.w"
3 3
 
4 4
 #include "scale.h"
@@ -10,7 +10,7 @@ QextSerialPort(port,QextSerialPort::EventDriven)
10 10
 connect(this,SIGNAL(readyRead()),this,SLOT(dataAvailable()));
11 11
 }
12 12
 
13
-/*:1020*//*1021:*/
13
+/*:1045*//*1046:*/
14 14
 #line 153 "./scales.w"
15 15
 
16 16
 void SerialScale::dataAvailable()
@@ -24,7 +24,7 @@ responseBuffer.clear();
24 24
 }
25 25
 else
26 26
 {
27
-/*1022:*/
27
+/*1047:*/
28 28
 #line 193 "./scales.w"
29 29
 
30 30
 QStringList responseParts= QString(responseBuffer.simplified()).split(' ');
@@ -53,7 +53,7 @@ unit= Units::Ounce;
53 53
 }
54 54
 emit newMeasurement(weight,unit);
55 55
 
56
-/*:1022*/
56
+/*:1047*/
57 57
 #line 165 "./scales.w"
58 58
 
59 59
 responseBuffer.clear();
@@ -61,7 +61,7 @@ responseBuffer.clear();
61 61
 }
62 62
 }
63 63
 
64
-/*:1021*//*1023:*/
64
+/*:1046*//*1048:*/
65 65
 #line 224 "./scales.w"
66 66
 
67 67
 void SerialScale::tare()
@@ -96,4 +96,4 @@ commandTerminator= "\x0A";
96 96
 }
97 97
 }
98 98
 
99
-/*:1023*/
99
+/*:1048*/

+ 2
- 2
src/scale.h 查看文件

@@ -1,4 +1,4 @@
1
-/*1019:*/
1
+/*1044:*/
2 2
 #line 103 "./scales.w"
3 3
 
4 4
 #ifndef TypicaScaleInclude
@@ -29,4 +29,4 @@ QByteArray commandTerminator;
29 29
 
30 30
 #endif
31 31
 
32
-/*:1019*/
32
+/*:1044*/

+ 3039
- 2532
src/typica.cpp
文件差異過大導致無法顯示
查看文件


+ 202
- 15
src/typica.w 查看文件

@@ -810,15 +810,25 @@ The first of these is |QObject|.
810 810
 
811 811
 @<Function prototypes for scripting@>=
812 812
 void setQObjectProperties(QScriptValue value, QScriptEngine *engine);
813
+QScriptValue QObject_setProperty(QScriptContext *context, QScriptEngine *engine);
813 814
 
814
-@ As there are no properties that need to be set for this class and as this
815
-class does not inherit any other class, nothing needs to be done in this method.
816
-It will, however, be called by subclasses in case this changes in the future.
815
+@ Attaching properties to a |QScriptValue| that wraps a |QObject| does not
816
+create a dynamic property on the underlying |QObject| by default. This can
817
+cause issues with certain interactions between script and native code. Rather
818
+than change every wrapper, we can instead expose a |setProperty()| method.
817 819
 
818 820
 @<Functions for scripting@>=
819
-void setQObjectProperties(QScriptValue, QScriptEngine *)
821
+void setQObjectProperties(QScriptValue value, QScriptEngine *engine)
820 822
 {
821
-    /* Nothing needs to be done here. */
823
+    value.setProperty("setProperty", engine->newFunction(QObject_setProperty));
824
+}
825
+
826
+QScriptValue QObject_setProperty(QScriptContext *context, QScriptEngine *)
827
+{
828
+	QObject *self = getself<QObject *>(context);
829
+	self->setProperty(argument<QString>(0, context).toUtf8().constData(),
830
+	                  argument<QVariant>(1, context));
831
+    return QScriptValue();
822 832
 }
823 833
 
824 834
 @ The same can be done for |QPaintDevice| and |QLayoutItem|.
@@ -1029,6 +1039,15 @@ considered depreciated.
1029 1039
 Version 1.6 adds a new property for handling the |windowModified| property
1030 1040
 such that an appropriate prompt is provided to confirm or cancel close events.
1031 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
+
1032 1051
 @<Class declarations@>=
1033 1052
 class ScriptQMainWindow : public QMainWindow@/
1034 1053
 {@t\1@>@/
@@ -1042,12 +1061,14 @@ class ScriptQMainWindow : public QMainWindow@/
1042 1061
         void saveSizeAndPosition(const QString &key);
1043 1062
         void restoreSizeAndPosition(const QString &key);
1044 1063
         void displayStatus(const QString &message = QString());
1045
-        void setClosePrompt(QString prompt);@/
1064
+        void setClosePrompt(QString prompt);
1065
+        void setupFinished();@/
1066
+    signals:@/
1067
+        void aboutToClose(void);
1068
+        void windowReady(void);@/
1046 1069
     protected:@/
1047 1070
         void closeEvent(QCloseEvent *event);
1048 1071
         void showEvent(QShowEvent *event);@/
1049
-    signals:@/
1050
-        void aboutToClose(void);@/
1051 1072
     private:@/
1052 1073
         QString cprompt;@t\2@>@/
1053 1074
 }@t\kern-3pt@>;
@@ -1108,6 +1129,11 @@ void ScriptQMainWindow::show()
1108 1129
     QMainWindow::show();
1109 1130
 }
1110 1131
 
1132
+void ScriptQMainWindow::setupFinished()
1133
+{
1134
+	emit windowReady();
1135
+}
1136
+
1111 1137
 @ When a close event occurs, we check the |windowModified| property to
1112 1138
 determine if closing the window could result in loss of data. If this is
1113 1139
 true, we allow the event to be cancelled. Otherwise, a signal is emitted which
@@ -4367,6 +4393,9 @@ Starting with version 1.4, we check for a command line option with the path to
4367 4393
 the configuration file and load that instead of prompting for the information
4368 4394
 if possible.
4369 4395
 
4396
+Starting with version 1.8, if there is not a -c argument, Typica will first
4397
+search a small number of locations relative to the executable.
4398
+
4370 4399
 @<Load the application configuration@>=
4371 4400
 QStringList arguments = QCoreApplication::arguments();
4372 4401
 int position = arguments.indexOf("-c");
@@ -4377,6 +4406,16 @@ if(position != -1)
4377 4406
     {
4378 4407
         filename = arguments.at(position + 1);
4379 4408
     }
4409
+} else {
4410
+	QDir checkPath(QCoreApplication::applicationDirPath() + "/../config/");
4411
+	if(checkPath.exists("config.xml")) {
4412
+		filename = checkPath.filePath("config.xml");
4413
+	} else {
4414
+		checkPath = QDir(QCoreApplication::applicationDirPath() + "/config/");
4415
+		if(checkPath.exists("config.xml")) {
4416
+			filename  = checkPath.filePath("config.xml");
4417
+		}
4418
+	}
4380 4419
 }
4381 4420
 if(filename.isEmpty())
4382 4421
 {
@@ -4401,6 +4440,8 @@ if(!filename.isEmpty())
4401 4440
     {
4402 4441
         app.configuration()->setContent(&file, true);
4403 4442
     }
4443
+} else {
4444
+	return 1;
4404 4445
 }
4405 4446
 @<Substitute included fragments@>@;
4406 4447
 
@@ -4571,6 +4612,8 @@ void addCalendarToLayout(QDomElement element, QStack<QWidget *> *widgetStack,
4571 4612
                          QStack<QLayout *> *layoutStack);
4572 4613
 void addSpinBoxToLayout(QDomElement element, QStack<QWidget *> *widgetStack,
4573 4614
                         QStack<QLayout *> *layoutStack);
4615
+void addTimeEditToLayout(QDomElement element, QStack<QWidget *> *widgetStack,
4616
+                         QStack<QLayout *> *layoutStack);
4574 4617
 
4575 4618
 @ The functions for creating windows must be made available to the scripting
4576 4619
 engine.
@@ -4679,6 +4722,7 @@ if(element.hasChildNodes())
4679 4722
 }
4680 4723
 @<Insert help menu@>@;
4681 4724
 window->show();
4725
+window->setupFinished();
4682 4726
 
4683 4727
 @ Three element types make sense as top level children of a {\tt <window>}
4684 4728
 element. An element representing a layout element can be used to apply that
@@ -4802,6 +4846,10 @@ while(j < menuItems.count())
4802 4846
         {
4803 4847
             menu->addSeparator();
4804 4848
         }
4849
+        else if(itemElement.tagName() == "plugins")
4850
+        {
4851
+	        @<Process plugin item@>@;
4852
+        }
4805 4853
     }
4806 4854
     j++;
4807 4855
 }
@@ -4915,6 +4963,80 @@ void populateStackedLayout(QDomElement element, QStack<QWidget *> *widgetStack,
4915 4963
     }
4916 4964
 }
4917 4965
 
4966
+@ A common use of stacked layouts is in the creation of tabbed interfaces, but
4967
+there are also many uses in \pn{} where the tabs are not required. Therefore,
4968
+tab bar creation requires a separate XML element.
4969
+
4970
+@<Additional box layout elements@>=
4971
+else if(currentElement.tagName() == "tabbar")
4972
+{
4973
+	addTabBarToLayout(currentElement, widgetStack, layoutStack);
4974
+}
4975
+
4976
+@ The function used to create this follows the usual pattern.
4977
+
4978
+@<Functions for scripting@>=
4979
+void addTabBarToLayout(QDomElement element, QStack<QWidget*> *, QStack<QLayout*> *layoutStack)
4980
+{
4981
+	QBoxLayout *layout = qobject_cast<QBoxLayout *>(layoutStack->top());
4982
+	QTabBar *widget = new QTabBar;
4983
+	layout->addWidget(widget);
4984
+	if(!element.attribute("id").isEmpty())
4985
+	{
4986
+		widget->setObjectName(element.attribute("id"));
4987
+	}
4988
+}
4989
+
4990
+@ Rather than define the tab set in XML, this is left to the host environment.
4991
+This means that some additional scripting support is required.
4992
+
4993
+@<Set up the scripting engine@>=
4994
+constructor = engine->newFunction(constructQTabBar);
4995
+value = engine->newQMetaObject(&QTabBar::staticMetaObject, constructor);
4996
+engine->globalObject().setProperty("QTabBar", value);
4997
+
4998
+@ The constructor is trivial.
4999
+
5000
+@<Functions for scripting@>=
5001
+QScriptValue constructQTabBar(QScriptContext *, QScriptEngine *engine)
5002
+{
5003
+	QScriptValue object = engine->newQObject(new QTabBar);
5004
+	setQTabBarProperties(object, engine);
5005
+	return object;
5006
+}
5007
+
5008
+@ There are many functions that I might want to some day add support for, but
5009
+the immediate need is just creating the tabs in the first place.
5010
+
5011
+@<Functions for scripting@>=
5012
+void setQTabBarProperties(QScriptValue value, QScriptEngine *engine)
5013
+{
5014
+	setQWidgetProperties(value, engine);
5015
+	value.setProperty("addTab", engine->newFunction(QTabBar_addTab));
5016
+}
5017
+
5018
+QScriptValue QTabBar_addTab(QScriptContext *context, QScriptEngine *)
5019
+{
5020
+	QTabBar *self = getself<QTabBar *>(context);
5021
+	if(context->argumentCount() > 0)
5022
+	{
5023
+		self->addTab(argument<QString>(0, context));
5024
+	}
5025
+	else
5026
+	{
5027
+		context->throwError("Incorrect number of arguments passed to "@|
5028
+		                    "QTabBar::addTab().");
5029
+	}
5030
+	return QScriptValue();
5031
+}
5032
+
5033
+@ Function prototypes are needed.
5034
+
5035
+@<Function prototypes for scripting@>=
5036
+QScriptValue constructQTabBar(QScriptContext *context, QScriptEngine *engine);
5037
+void setQTabBarProperties(QScriptValue value, QScriptEngine *engine);
5038
+QScriptValue QTabBar_addTab(QScriptContext *context, QScriptEngine *engine);
5039
+
4918 5040
 @ Using a grid layout is a bit different from using a box layout. Child elements
4919 5041
 with various attributes are required to take full advantage of this layout type.
4920 5042
 All direct children of a grid layout element should be {\tt <row>} elements
@@ -5050,6 +5172,10 @@ void populateBoxLayout(QDomElement element, QStack<QWidget *> *widgetStack,
5050 5172
             {
5051 5173
                 addCalendarToLayout(currentElement, widgetStack, layoutStack);
5052 5174
             }
5175
+            else if(currentElement.tagName() == "timeedit")
5176
+            {
5177
+	            addTimeEditToLayout(currentElement, widgetStack, layoutStack);
5178
+            }
5053 5179
             else if(currentElement.tagName() == "decoration")
5054 5180
             {
5055 5181
                 addDecorationToLayout(currentElement, widgetStack,
@@ -6203,6 +6329,43 @@ QScriptValue QDateTimeEdit_month(QScriptContext *context,
6203 6329
 QScriptValue QDateTimeEdit_year(QScriptContext *context, QScriptEngine *engine);
6204 6330
 QScriptValue QDateTimeEdit_setToCurrentTime(QScriptContext *context, QScriptEngine *engine);
6205 6331
 
6332
+@ Sometimes it can be useful to allow editing a time or duration value without
6333
+a date field. For this, a |QTimeEdit| can be used.
6334
+
6335
+@<Functions for scripting@>=
6336
+void addTimeEditToLayout(QDomElement element, QStack<QWidget *> *,@|
6337
+                         QStack<QLayout *> *layoutStack)
6338
+{
6339
+	QTimeEdit *edit = new QTimeEdit;
6340
+	if(element.hasAttribute("displayFormat"))
6341
+	{
6342
+		edit->setDisplayFormat(element.attribute("displayFormat"));
6343
+	}
6344
+	else
6345
+	{
6346
+		edit->setDisplayFormat("mm:ss.zzz");
6347
+	}
6348
+	if(element.hasAttribute("id"))
6349
+	{
6350
+		edit->setObjectName(element.attribute("id"));
6351
+	}
6352
+	QBoxLayout *layout = qobject_cast<QBoxLayout *>(layoutStack->top());
6353
+	layout->addWidget(edit);
6354
+}
6355
+
6356
+@ Additional properties are added as a |QTimeEdit| is a |QDateTimeEdit|.
6357
+
6358
+@<Functions for scripting@>=
6359
+void setQTimeEditProperties(QScriptValue value, QScriptEngine *engine)
6360
+{
6361
+	setQDateTimeEditProperties(value, engine);
6362
+}
6363
+
6364
+@ A function prototype is needed.
6365
+
6366
+@<Function prototypes for scripting@>=
6367
+void setQTimeEditProperties(QScriptValue value, QScriptEngine *engine);
6368
+
6206 6369
 @ In order to get to objects created from the XML description, it is necessary
6207 6370
 to provide a function that can be called to retrieve children of a given widget.
6208 6371
 When providing such an object to the script, it is necessary to determine the
@@ -6342,6 +6505,14 @@ else if(className == "QSvgWidget")
6342 6505
 {
6343 6506
     setQSvgWidgetProperties(value, engine);
6344 6507
 }
6508
+else if(className == "QTabBar")
6509
+{
6510
+	setQTabBarProperties(value, engine);
6511
+}
6512
+else if(className == "PrinterSelector")
6513
+{
6514
+	setQComboBoxProperties(value, engine);
6515
+}
6345 6516
 
6346 6517
 @ In the list of classes, the SaltTable entry is for a class which does not
6347 6518
 strictly exist on its own. It is, however, useful to provide some custom
@@ -9004,6 +9175,9 @@ space, but it is very fast and simple to code.
9004 9175
 Starting in version 1.4, column sizes are persisted automatically using the
9005 9176
 same method as described in the section on |SqlQueryView|.
9006 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
+
9007 9181
 @<Class declarations@>=
9008 9182
 class MeasurementModel;@/
9009 9183
 class ZoomLog : public QTableView@/
@@ -9018,7 +9192,7 @@ class ZoomLog : public QTableView@/
9018 9192
     public:@/
9019 9193
         ZoomLog();
9020 9194
         QVariant data(int row, int column) const;
9021
-        int rowCount();
9195
+        @[Q_INVOKABLE@,@, int rowCount();
9022 9196
         bool saveXML(QIODevice *device);
9023 9197
         bool saveCSV(QIODevice *device);
9024 9198
         QString lastTime(int series);
@@ -9150,11 +9324,11 @@ annotation associated with it. The solution in this case is to synthesize
9150 9324
 measurements so that the |ZoomLog| thinks it gets at least one measurement
9151 9325
 every second.
9152 9326
 
9153
-The current approach simply replicates the last measurement every second until
9154
-the time for the most recent measurement is reached, however it would likely be
9155
-better to interpolate values between the two most recent real measurements as
9156
-this would match the graphic representation rather than altering it when later
9157
-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.
9158 9332
 
9159 9333
 @<Synthesize measurements for slow hardware@>=
9160 9334
 if(lastMeasurement.contains(tempcolumn))
@@ -9162,14 +9336,23 @@ if(lastMeasurement.contains(tempcolumn))
9162 9336
     if(lastMeasurement[tempcolumn].time() < measure.time())
9163 9337
     {
9164 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();
9165 9345
         for(QTime i = lastMeasurement.value(tempcolumn).time().addSecs(1); i < measure.time(); i = i.addSecs(1))
9166 9346
         {
9167 9347
             timelist.append(i);
9348
+            double v = ((ptemp * (ctime - z.secsTo(i))) + (ctemp * (z.secsTo(i) - ptime))) / (ctime - ptime);
9349
+            templist.append(v);
9168 9350
         }
9169 9351
         for(int i = 0; i < timelist.size(); i++)
9170 9352
         {
9171 9353
             Measurement synthesized = measure;
9172 9354
             synthesized.setTime(timelist[i]);
9355
+            synthesized.setTemperature(templist[i]);
9173 9356
             newMeasurement(synthesized, tempcolumn);
9174 9357
         }
9175 9358
     }
@@ -12617,6 +12800,8 @@ void CSVOutput::setDevice(QIODevice *device)
12617 12800
 
12618 12801
 @i webview.w
12619 12802
 
12803
+@i printerselector.w
12804
+
12620 12805
 @* The Application class.
12621 12806
 
12622 12807
 The |Application| class represents the \pn{} program. It is responsible for
@@ -14346,6 +14531,8 @@ void setQTextEditProperties(QScriptValue value, QScriptEngine *engine)
14346 14531
     value.setProperty("print", engine->newFunction(QTextEdit_print));
14347 14532
 }
14348 14533
 
14534
+@i plugins.w
14535
+
14349 14536
 @i daterangeselector.w
14350 14537
 
14351 14538
 @** An area for repeated user interface elements.
@@ -15378,7 +15565,7 @@ class DeviceTreeModel : public QAbstractItemModel@/
15378 15565
         QModelIndex index(int row, int column,
15379 15566
                           const QModelIndex &parent = QModelIndex()) const;
15380 15567
         QModelIndex parent(const QModelIndex &child) const;
15381
-        int rowCount(const QModelIndex &parent = QModelIndex()) const;
15568
+        Q_INVOKABLE int rowCount(const QModelIndex &parent = QModelIndex()) const;
15382 15569
         int columnCount(const QModelIndex &parent = QModelIndex()) const;
15383 15570
         bool setData(const QModelIndex &index, const QVariant &value,
15384 15571
                      int role);

+ 6
- 6
src/units.cpp 查看文件

@@ -1,10 +1,10 @@
1
-/*279:*/
1
+/*291:*/
2 2
 #line 42 "./units.w"
3 3
 
4 4
 #include "units.h"
5 5
 #include <QtDebug> 
6 6
 
7
-/*:279*//*280:*/
7
+/*:291*//*292:*/
8 8
 #line 53 "./units.w"
9 9
 
10 10
 bool Units::isTemperatureUnit(Unit unit)
@@ -15,7 +15,7 @@ unit==Kelvin||
15 15
 unit==Rankine);
16 16
 }
17 17
 
18
-/*:280*//*281:*/
18
+/*:292*//*293:*/
19 19
 #line 71 "./units.w"
20 20
 
21 21
 double Units::convertTemperature(double value,Unit fromUnit,Unit toUnit)
@@ -113,7 +113,7 @@ break;
113 113
 return 0;
114 114
 }
115 115
 
116
-/*:281*//*282:*/
116
+/*:293*//*294:*/
117 117
 #line 169 "./units.w"
118 118
 
119 119
 double Units::convertRelativeTemperature(double value,Unit fromUnit,Unit toUnit)
@@ -211,7 +211,7 @@ break;
211 211
 return 0;
212 212
 }
213 213
 
214
-/*:282*//*283:*/
214
+/*:294*//*295:*/
215 215
 #line 267 "./units.w"
216 216
 
217 217
 double Units::convertWeight(double value,Unit fromUnit,Unit toUnit)
@@ -316,4 +316,4 @@ unit==Ounce||
316 316
 unit==Gram);
317 317
 }
318 318
 
319
-/*:283*/
319
+/*:295*/

+ 2
- 2
src/units.h 查看文件

@@ -1,4 +1,4 @@
1
-/*278:*/
1
+/*290:*/
2 2
 #line 8 "./units.w"
3 3
 
4 4
 #include <QObject> 
@@ -32,4 +32,4 @@ static bool isWeightUnit(Unit unit);
32 32
 
33 33
 #endif
34 34
 
35
-/*:278*/
35
+/*:290*/

+ 9
- 9
src/webelement.cpp 查看文件

@@ -1,18 +1,18 @@
1
-/*573:*/
2
-#line 368 "./webview.w"
1
+/*586:*/
2
+#line 383 "./webview.w"
3 3
 
4 4
 #include "webelement.h"
5 5
 
6
-/*571:*/
7
-#line 311 "./webview.w"
6
+/*584:*/
7
+#line 326 "./webview.w"
8 8
 
9 9
 TypicaWebElement::TypicaWebElement(QWebElement element):e(element)
10 10
 {
11 11
 
12 12
 }
13 13
 
14
-/*:571*//*572:*/
15
-#line 320 "./webview.w"
14
+/*:584*//*585:*/
15
+#line 335 "./webview.w"
16 16
 
17 17
 void TypicaWebElement::appendInside(const QString&markup)
18 18
 {
@@ -59,8 +59,8 @@ void TypicaWebElement::setPlainText(const QString&text)
59 59
 e.setPlainText(text);
60 60
 }
61 61
 
62
-/*:572*/
63
-#line 371 "./webview.w"
62
+/*:585*/
63
+#line 386 "./webview.w"
64 64
 
65 65
 
66
-/*:573*/
66
+/*:586*/

+ 3
- 3
src/webelement.h 查看文件

@@ -1,5 +1,5 @@
1
-/*566:*/
2
-#line 248 "./webview.w"
1
+/*579:*/
2
+#line 263 "./webview.w"
3 3
 
4 4
 #include <QWebElement> 
5 5
 #include <QObject> 
@@ -27,4 +27,4 @@ QWebElement e;
27 27
 
28 28
 #endif
29 29
 
30
-/*:566*/
30
+/*:579*/

+ 32
- 22
src/webview.cpp 查看文件

@@ -1,9 +1,9 @@
1
-/*551:*/
1
+/*563:*/
2 2
 #line 50 "./webview.w"
3 3
 
4 4
 #include "webview.h"
5 5
 
6
-/*552:*/
6
+/*564:*/
7 7
 #line 57 "./webview.w"
8 8
 
9 9
 TypicaWebView::TypicaWebView():QWebView()
@@ -12,7 +12,7 @@ page()->setLinkDelegationPolicy(QWebPage::DelegateExternalLinks);
12 12
 connect(page(),SIGNAL(linkClicked(QUrl)),this,SLOT(linkDelegate(QUrl)));
13 13
 }
14 14
 
15
-/*:552*//*553:*/
15
+/*:564*//*565:*/
16 16
 #line 73 "./webview.w"
17 17
 
18 18
 void TypicaWebView::linkDelegate(const QUrl&url)
@@ -20,7 +20,7 @@ void TypicaWebView::linkDelegate(const QUrl&url)
20 20
 if(url.scheme()=="typica")
21 21
 {
22 22
 QString address(url.toEncoded());
23
-/*554:*/
23
+/*566:*/
24 24
 #line 91 "./webview.w"
25 25
 
26 26
 if(address=="typica://aboutqt")
@@ -29,10 +29,10 @@ QMessageBox::aboutQt(this);
29 29
 return;
30 30
 }
31 31
 
32
-/*:554*/
32
+/*:566*/
33 33
 #line 79 "./webview.w"
34 34
 
35
-/*555:*/
35
+/*567:*/
36 36
 #line 100 "./webview.w"
37 37
 
38 38
 if(address.startsWith("typica://script/"))
@@ -41,7 +41,7 @@ emit scriptLinkClicked(address.remove(0,16));
41 41
 return;
42 42
 }
43 43
 
44
-/*:555*/
44
+/*:567*/
45 45
 #line 80 "./webview.w"
46 46
 
47 47
 }
@@ -51,7 +51,7 @@ QDesktopServices::openUrl(url);
51 51
 }
52 52
 }
53 53
 
54
-/*:553*//*556:*/
54
+/*:565*//*568:*/
55 55
 #line 112 "./webview.w"
56 56
 
57 57
 void TypicaWebView::load(const QString&url)
@@ -59,16 +59,6 @@ void TypicaWebView::load(const QString&url)
59 59
 QWebView::load(QUrl(url));
60 60
 }
61 61
 
62
-void TypicaWebView::print()
63
-{
64
-QPrinter*printer= new QPrinter(QPrinter::HighResolution);
65
-QPrintDialog printDialog(printer,NULL);
66
-if(printDialog.exec()==QDialog::Accepted)
67
-{
68
-QWebView::print(printer);
69
-}
70
-}
71
-
72 62
 void TypicaWebView::setHtml(const QString&html,const QUrl&baseUrl)
73 63
 {
74 64
 QWebView::setHtml(html,baseUrl);
@@ -88,8 +78,28 @@ QString TypicaWebView::saveXml()
88 78
 return page()->currentFrame()->documentElement().toOuterXml();
89 79
 }
90 80
 
91
-/*:556*//*562:*/
92
-#line 205 "./webview.w"
81
+/*:568*//*569:*/
82
+#line 144 "./webview.w"
83
+
84
+void TypicaWebView::print(const QString&printerName)
85
+{
86
+QPrinter*printer= new QPrinter(QPrinter::HighResolution);
87
+if(!printerName.isEmpty())
88
+{
89
+printer->setPrinterName(printerName);
90
+printer->setFullPage(true);
91
+QWebView::print(printer);
92
+return;
93
+}
94
+QPrintDialog printDialog(printer,NULL);
95
+if(printDialog.exec()==QDialog::Accepted)
96
+{
97
+QWebView::print(printer);
98
+}
99
+}
100
+
101
+/*:569*//*575:*/
102
+#line 220 "./webview.w"
93 103
 
94 104
 QWebElement TypicaWebView::documentElement()
95 105
 {
@@ -101,8 +111,8 @@ QWebElement TypicaWebView::findFirstElement(const QString&selector)
101 111
 return page()->mainFrame()->findFirstElement(selector);
102 112
 }
103 113
 
104
-/*:562*/
114
+/*:575*/
105 115
 #line 53 "./webview.w"
106 116
 
107 117
 
108
-/*:551*/
118
+/*:563*/

+ 3
- 3
src/webview.h 查看文件

@@ -1,4 +1,4 @@
1
-/*550:*/
1
+/*562:*/
2 2
 #line 14 "./webview.w"
3 3
 
4 4
 #include <QWebView> 
@@ -20,7 +20,7 @@ Q_OBJECT
20 20
 public:
21 21
 TypicaWebView();
22 22
 Q_INVOKABLE void load(const QString&url);
23
-Q_INVOKABLE void print();
23
+Q_INVOKABLE void print(const QString&printerName= QString());
24 24
 Q_INVOKABLE void setHtml(const QString&html,const QUrl&baseUrl= QUrl());
25 25
 Q_INVOKABLE void setContent(QIODevice*device);
26 26
 Q_INVOKABLE QString saveXml();
@@ -34,4 +34,4 @@ void linkDelegate(const QUrl&url);
34 34
 
35 35
 #endif
36 36
 
37
-/*:550*/
37
+/*:562*/

+ 26
- 11
src/webview.w 查看文件

@@ -31,7 +31,7 @@ class TypicaWebView : public QWebView@/
31 31
 	public:@/
32 32
 		TypicaWebView();
33 33
 		@[Q_INVOKABLE@,@, void@]@, load(const QString &url);@t\2\2@>@/
34
-		@[Q_INVOKABLE@,@, void@]@, print();@t\2\2@>@/
34
+		@[Q_INVOKABLE@,@, void@]@, print(const QString &printerName = QString());@t\2\2@>@/
35 35
 		@[Q_INVOKABLE@,@, void@]@, setHtml(const QString &html, const QUrl &baseUrl = QUrl());@t\2\2@>@/
36 36
 		@[Q_INVOKABLE@,@, void@]@, setContent(QIODevice *device);@t\2\2@>@/
37 37
 		@[Q_INVOKABLE@,@, QString@]@, saveXml();@t\2\2@>@/
@@ -115,16 +115,6 @@ void TypicaWebView::load(const QString &url)
115 115
 	QWebView::load(QUrl(url));
116 116
 }
117 117
 
118
-void TypicaWebView::print()
119
-{
120
-	QPrinter *printer = new QPrinter(QPrinter::HighResolution);
121
-	QPrintDialog printDialog(printer, NULL);
122
-	if(printDialog.exec() == QDialog::Accepted)
123
-	{
124
-		QWebView::print(printer);
125
-	}
126
-}
127
-
128 118
 void TypicaWebView::setHtml(const QString &html, const QUrl &baseUrl)
129 119
 {
130 120
 	QWebView::setHtml(html, baseUrl);
@@ -144,6 +134,31 @@ QString TypicaWebView::saveXml()
144 134
 	return page()->currentFrame()->documentElement().toOuterXml();
145 135
 }
146 136
 
137
+@ Print functionality has been extended to allow an optional argument. If the
138
+name of a printer is passed in, the print dialog will be bypassed. Note that
139
+when bypassing the print dialog, page margins are set to 0. This is intentional
140
+as the use case is for printing to more specialized printers where default page
141
+margins are not appropriate and CSS can be customized to ensure that all
142
+information fits in the printable area with the assumption of no margin.
143
+
144
+@<TypicaWebView implementation@>=
145
+void TypicaWebView::print(const QString &printerName)
146
+{
147
+	QPrinter *printer = new QPrinter(QPrinter::HighResolution);
148
+	if(!printerName.isEmpty())
149
+	{
150
+		printer->setPrinterName(printerName);
151
+		printer->setFullPage(true);
152
+		QWebView::print(printer);
153
+		return;
154
+	}
155
+	QPrintDialog printDialog(printer, NULL);
156
+	if(printDialog.exec() == QDialog::Accepted)
157
+	{
158
+		QWebView::print(printer);
159
+	}
160
+}
161
+
147 162
 @ Web views are exposed to the host environment in the usual manner.
148 163
 
149 164
 @<Set up the scripting engine@>=

Loading…
取消
儲存