Added print checklist

Mon, 30 May 2022 21:44:40 +0200

author
Michiel Broek <mbroek@mbse.eu>
date
Mon, 30 May 2022 21:44:40 +0200
changeset 240
52efe638e79a
parent 239
bfd4e07c20cb
child 241
9b394e5a8d8d

Added print checklist

src/EditProduct.h file | annotate | diff | comparison | revisions
src/EditProductExport.cpp file | annotate | diff | comparison | revisions
src/PrinterDialog.cpp file | annotate | diff | comparison | revisions
src/PrinterDialog.h file | annotate | diff | comparison | revisions
--- a/src/EditProduct.h	Sat May 28 21:08:09 2022 +0200
+++ b/src/EditProduct.h	Mon May 30 21:44:40 2022 +0200
@@ -306,6 +306,7 @@
     void calcPack();
     void exportBeerXML();
     void printProduct();
+    void printChecklist();
 };
 
 #endif
--- a/src/EditProductExport.cpp	Sat May 28 21:08:09 2022 +0200
+++ b/src/EditProductExport.cpp	Mon May 30 21:44:40 2022 +0200
@@ -268,6 +268,12 @@
 }
 
 
+void EditProduct::printChecklist()
+{
+    PrinterDialog(PR_CHECKLIST, -1, this);
+}
+
+
 void EditProduct::on_exportButton_clicked()
 {
     exportBeerXML();
@@ -276,7 +282,7 @@
 
 void EditProduct::on_printButton_clicked()
 {
-    printProduct();
+    printChecklist();
 }
 
 
--- a/src/PrinterDialog.cpp	Sat May 28 21:08:09 2022 +0200
+++ b/src/PrinterDialog.cpp	Mon May 30 21:44:40 2022 +0200
@@ -1522,12 +1522,604 @@
                 y += 20;
             }
 	}
+
+    } else if (p_job == PR_CHECKLIST) {
+
+	double	factor = 1.0 / product->divide_factor;
+	double	mashwater = 0;
+	int	numsalts = 0;
+        qDebug() << "print checklist";
+        printHeader(&painter);
+        y = 120;
+
+	/* First item, a yeast starter if needed, days before brewday. */
+	if (product->starter_enable && product->prop_volume[0]) {
+	    checkHeader(&painter, &y, tr("Make a yeast starter"));
+	    int	days = 0;
+	    int	last = 0;
+	    QString s = "";
+	    for (int i = 0; i < 4; i++) {
+		if (product->prop_volume[i]) {
+		    last = i;
+		    if (product->prop_type[i] == STARTERS_STIRRED)
+			days += 2;
+		    else if (product->prop_type[i] == STARTERS_SHAKEN)
+			days += 4;
+		    else
+			days += 6;	// Simple starter
+		}
+	    }
+	    checkLine(&painter, &y, QString(tr("Start about %1 days before brewday with the starter.")).arg(days));
+	    for (int i = 0; i < 4; i++) {
+		if (product->prop_volume[i]) {
+		    checkLine(&painter, &y, QString(tr("Starter step %1 of %2 liter with SG %3")).arg(i+1).arg(product->prop_volume[i], 1, 'f', 3).
+				arg(product->starter_sg, 1, 'f', 3));
+		    QString w = tr(" until there is enough yeast");
+		    if (product->prop_type[i] == STARTERS_STIRRED)
+		    	checkLine(&painter, &y, QString(tr("about 24 hours on a stirplate"))+w);
+		    else if (product->prop_type[i] == STARTERS_SHAKEN)
+		    	checkLine(&painter, &y, QString(tr("shake often for a few days"))+w);
+		    else
+		    	checkLine(&painter, &y, QString(tr("let it rest for almost a week"))+w);
+		    if (i < last) {
+		    	checkLine(&painter, &y, QString(tr("place starter in the fridge for 24 hours")));
+		    	checkLine(&painter, &y, QString(tr("remove starter from the fridge and decant")));
+		    } else {
+		    	checkLine(&painter, &y, QString(tr("place starter in the fridge until brewday")));
+		    	checkLine(&painter, &y, QString(tr("remove starter from the fridge and decant")));
+		    }
+		}
+	    }
+	    y += 20;
+	}
+
+	checkHeader(&painter, &y, tr("Mash water and treatment"));
+	checkLine(&painter, &y, QString("%1 liter water %2").arg(product->w1_amount * factor, 1, 'f', 1).arg(product->w1_name));
+	mashwater += product->w1_amount * factor;
+	if (product->w2_name != "" && product->w2_amount > 0) {
+	    checkLine(&painter, &y, QString("%1 liter water %2").arg(product->w2_amount * factor, 1, 'f', 1).arg(product->w2_name));
+	    mashwater += product->w2_amount * factor;
+	}
+	for (int i = 0; i < product->miscs.size(); i++) {
+	    if (product->miscs.at(i).m_type == MISC_TYPES_WATER_AGENT) {
+		QString unit = (product->miscs.at(i).m_amount_is_weight) ? "gr":"ml";
+		checkLine(&painter, &y, QString("%1 %2 %3").arg(product->miscs.at(i).m_amount * 1000 * factor, 1, 'f', 2).arg(unit).arg(product->miscs.at(i).m_name));
+		numsalts++;
+	    }
+	}
+	y += 20;
+
+	checkHeader(&painter, &y, tr("Weight and mill the malts"));
+	for (int i = 0; i < product->fermentables.size(); i++) {
+	    if (product->fermentables.at(i).f_added == FERMENTABLE_ADDED_MASH) {
+		checkLine(&painter, &y, QString("%1 kg `%2` (%3)").arg(product->fermentables.at(i).f_amount * factor, 1, 'f', 3).
+				arg(product->fermentables.at(i).f_name).arg(product->fermentables.at(i).f_supplier));
+	    }
+	}
+	checkLine(&painter, &y, QString(tr("Mill the malts")));
+	y += 20;
+
+	checkHeader(&painter, &y, tr("Mash"));
+	int loop = 0;
+	double l, mvol = 0, msugars = 0;
+	for (int i = 0; i < product->mashs.size(); i++) {
+	    if (product->mashs.at(i).step_type == 0)
+		mvol += product->mashs.at(i).step_infuse_amount * factor;	// We need this later
+	    if (loop == 0) {
+		if (product->mashs.at(i).step_type == 0)
+		    l = product->mashs.at(i).step_infuse_amount * factor;
+		else
+		    l = mashwater;
+		checkLine(&painter, &y, QString(tr("Heat %1 liter water to %2°C (%3 cm below kettle top)"))
+			.arg(mashwater, 1, 'f', 1).arg(product->mashs.at(i).step_infuse_temp, 1, 'f', 1)
+			.arg(Utils::kettle_cm(l, product->eq_tun_volume, product->eq_tun_height), 1, 'f', 1));
+		if (numsalts > 0)
+		    checkLine(&painter, &y, QString(tr("Add brouwzouten")));
+		checkLine(&painter, &y, QString(tr("Add malts and dough-in")));
+		for (int j = 0; j < product->hops.size(); j++) {
+		    if (product->hops.at(j).h_useat == HOP_USEAT_MASH) {
+			checkLine(&painter, &y, QString(tr("Add %1 gram `%2` hop")).arg(product->hops.at(j).h_amount * 1000 * factor, 1, 'f', 1)
+			       .arg(product->hops.at(j).h_name));
+		    }
+		}
+		for (int j = 0; j < product->miscs.size(); j++) {
+		    if ((product->miscs.at(j).m_use_use == MISC_USES_MASH) && (product->miscs.at(j).m_type != MISC_TYPES_WATER_AGENT)) {
+			QString unit = (product->miscs.at(j).m_amount_is_weight) ? "gr":"ml";
+			checkLine(&painter, &y, QString(tr("Add %1 %2 `%3`")).arg(product->miscs.at(j).m_amount * 1000 * factor, 1, 'f', 2)
+				.arg(unit).arg(product->miscs.at(j).m_name));
+		    }
+		}
+	    } else {	// loop > 0
+		if (product->mashs.at(i).step_type == 0) {	// Infusion
+		    checkLine(&painter, &y, QString(tr("Add %1 liter water of %2°C")).arg(product->mashs.at(i).step_infuse_amount * factor, 1, 'f', 1)
+				.arg(product->mashs.at(i).step_infuse_temp, 1, 'f', 1));
+		} else if (product->mashs.at(i).step_type == 1) {	// Direct heat
+		    checkLine(&painter, &y, QString(tr("Heat upto %1°C")).arg(product->mashs.at(i).step_temp, 1, 'f', 1));
+		} else {	// Decoction
+		    checkLine(&painter, &y, QString(tr("Take, heat, boil and return %1 part of the mash"))
+				.arg(product->mashs.at(i).step_infuse_amount * factor, 1, 'f', 1));
+		}
+	    }
+	    if (product->mashs.at(i).step_temp != product->mashs.at(i).end_temp) {
+                checkInput(&painter, &y, QString(tr("%1 minutes from %2°C to %3°C")).arg(product->mashs.at(i).step_time)
+                                .arg(product->mashs.at(i).step_temp, 1, 'f', 1).arg(product->mashs.at(i).end_temp, 1, 'f', 1), QString(tr("Brix")));
+            } else {
+                checkInput(&painter, &y, QString(tr("%1 minutes at %2°C")).arg(product->mashs.at(i).step_time)
+                                .arg(product->mashs.at(i).step_temp, 1, 'f', 1), QString(tr("Brix")));
+            }
+	    if (loop == 0)
+		checkInput(&painter, &y, QString(tr("Measure and adjust pH (target %1 pH)")).arg(product->mash_ph, 1, 'f', 2), QString(tr("pH")));
+	    loop++;
+	}
+	double est_masg_sg = 0, sugardensity = 1.611, grainabsorbtion = 0;
+	for (int i = 0; i < product->fermentables.size(); i++) {
+	    if (product->fermentables.at(i).f_added == FERMENTABLE_ADDED_MASH) {
+		double d = product->fermentables.at(i).f_amount * factor * (product->fermentables.at(i).f_yield / 100) *
+			   (1 - product->fermentables.at(i).f_moisture / 100);
+		mvol += product->fermentables.at(i).f_amount * factor * (product->fermentables.at(i).f_moisture / 100);
+		grainabsorbtion += my_grain_absorbtion * product->fermentables.at(i).f_amount * factor;
+		msugars += d;
+	    }
+	}
+	double v = msugars / sugardensity + mvol;
+	double plato = 1000.0 * msugars / (v * 10.0);	// deg. Plato
+	double mash_sg = Utils::plato_to_sg(plato);
+	checkInput(&painter, &y, QString(tr("Target SG end mash: ")) + strDensity(mash_sg), QString(tr("SG")));
+	if ((y + 140) > painter.device()->height()) {
+            printer->newPage();
+            printHeader(&painter);
+            y = 120;
+	} else {
+            y += 20;
+	}
+
+	checkHeader(&painter, &y, tr("Lauter and Sparge"));
+	checkLine(&painter, &y, QString(tr("Heat %1 liter sparge water to %2°C")).arg(product->sparge_volume * factor, 1, 'f', 1)
+			.arg(product->sparge_temp, 1, 'f', 1));
+	checkInput(&painter, &y, QString(tr("Bring to %1 pH with %2 ml. `%3`")).arg(product->sparge_ph, 1, 'f', 2)
+			.arg(product->sparge_acid_amount * 1000 * factor, 1, 'f', 2).arg(my_acids.at(product->sparge_acid_type).name_en), QString(tr("pH")));
+	checkLine(&painter, &y, QString(tr("Sparge with close to %1 liter water"))
+			.arg(((product->boil_size * factor) - mashwater + grainabsorbtion + product->eq_lauter_deadspace) * 1.03, 1, 'f', 1));
+	checkInput(&painter, &y, QString(tr("Target volume in boil kettle: %1 liter (%2 cm below kettle top)"))
+			.arg(product->boil_size * factor * 1.04, 1, 'f', 1)
+			.arg(Utils::kettle_cm(product->boil_size * factor * 1.04, product->eq_kettle_volume, product->eq_kettle_height), 1, 'f', 1),
+			QString(tr("cm")));
+	checkInput(&painter, &y, QString(tr("Target SG in boil kettle: ")) + strDensity(product->preboil_sg), QString(tr("SG")));
+	checkInput(&painter, &y, "", QString(tr("pH")));
+	for (int i = 0; i < product->hops.size(); i++) {
+	    if (product->hops.at(i).h_useat == HOP_USEAT_FWH) {
+		checkLine(&painter, &y, QString(tr("Add %1 gr `%2` hop after sparge")).arg(product->hops.at(i).h_amount * 1000 * factor, 1, 'f', 1)
+			.arg(product->hops.at(i).h_name));
+	    }
+	}
+
+	if (checkSplit(&painter, &y, 1))
+	    factor = 1;
+	/* Boil, how much space do we need */
+	int lines = 0;
+	if (product->boil_time == 0) {
+	   lines = 3;
+	} else {
+	   lines = 5;
+	   if (product->brew_cooling_method == 1)
+		lines++;
+	   for (int i = 0; i < product->fermentables.size(); i++) {
+		if (product->fermentables.at(i).f_added == FERMENTABLE_ADDED_BOIL)
+		    lines++;
+	   }
+	   for (int i = 0; i < product->hops.size(); i++) {
+		if (product->hops.at(i).h_useat == HOP_USEAT_BOIL || product->hops.at(i).h_useat == HOP_USEAT_AROMA)
+		    lines++;
+	   }
+	   for (int i = 0; i < product->miscs.size(); i++) {
+		if (product->miscs.at(i).m_use_use == MISC_USES_BOIL)
+		    lines++;
+	   }
+	}
+	qDebug() << "check" << lines << y + (lines * 20) << painter.device()->height();
+	if ((y + (lines * 20)) > painter.device()->height()) {
+            printer->newPage();
+            printHeader(&painter);
+            y = 120;
+        } else {
+            y += 20;
+        }
+	checkHeader(&painter, &y, tr("Boil"));
+	if (product->boil_time > 0) {
+	    checkLine(&painter, &y, QString(tr("Total boiltime: %1 minutes")).arg(product->boil_time, 1, 'f', 0));
+	    for (int i = product->boil_time; i >= 0; i--) {
+		if (i == 10) {
+		    for (int j = 0; j < product->fermentables.size(); j++) {
+			if (product->fermentables.at(j).f_added == FERMENTABLE_ADDED_BOIL)
+			    checkLine(&painter, &y, QString(tr("%1 kg `%2` at 10 minutes before end of boil"))
+				.arg(product->fermentables.at(j).f_amount * factor, 1, 'f', 3)
+				.arg(product->fermentables.at(j).f_name));
+		    }
+		    if (product->brew_cooling_method == 1)
+			checkLine(&painter, &y, QString(tr("Place emersion chiller at 10 minutes before end of boil")));
+		}
+		for (int j = 0; j < product->hops.size(); j++) {
+		    if ((product->hops.at(j).h_useat == HOP_USEAT_BOIL || product->hops.at(j).h_useat == HOP_USEAT_AROMA) && product->hops.at(j).h_time == i) {
+			if (i == 0)
+			    checkLine(&painter, &y, QString(tr("%1 gr `%2` at flameout")).arg(product->hops.at(j).h_amount * 1000 * factor, 1, 'f', 2)
+				.arg(product->hops.at(j).h_name));
+			else
+			    checkLine(&painter, &y, QString(tr("%1 gr `%2` at %3 minutes before end of boil"))
+				.arg(product->hops.at(j).h_amount * 1000 * factor, 1, 'f', 2).arg(product->hops.at(j).h_name).arg(i));
+		    }
+		}
+		for (int j = 0; j < product->miscs.size(); j++) {
+		    if (product->miscs.at(j).m_use_use == MISC_USES_BOIL && product->miscs.at(j).m_time == i) {
+			QString unit = (product->miscs.at(j).m_amount_is_weight) ? "gr":"ml";
+			if (i == 0)
+			    checkLine(&painter, &y, QString(tr("%1 %2 `%3` at flameout"))
+				.arg(product->miscs.at(j).m_amount * 1000 * factor, 1, 'f', 2).arg(unit).arg(product->miscs.at(j).m_name));
+			else
+			    checkLine(&painter, &y, QString(tr("%1 %2 `%3` at %4 minutes before end of boil"))
+				.arg(product->miscs.at(j).m_amount * 1000 * factor, 1, 'f', 2).arg(unit).arg(product->miscs.at(j).m_name).arg(i));
+		    }
+		}
+	    }
+	    checkInput(&painter, &y, QString(tr("Target volume at end of boil: %1 liter (%2 cm below kettle top)"))
+			.arg(product->batch_size * 1.04 * factor, 1, 'f', 1)
+			.arg(Utils::kettle_cm(product->batch_size * 1.04 * factor, product->eq_kettle_volume, product->eq_kettle_height)),
+			QString(tr("cm")));
+	    checkInput(&painter, &y, QString(tr("Target SG at end of boil: ")) + strDensity(product->est_og3), QString(tr("SG")));
+	    checkInput(&painter, &y, "", QString(tr("pH")));
+	    if (checkSplit(&painter, &y, 2))
+            	factor = 1;
+	} else {
+	    checkLine(&painter, &y, QString(tr("This is a `no-boil` recipe")));
+	}
+
+	/* Whirlpools and chilling */
+	lines = 5;
+	if (product->brew_whirlpool9)
+	    lines++;
+	if (product->brew_whirlpool7)
+            lines++;
+	if (product->brew_whirlpool6)
+            lines++;
+	if (product->brew_whirlpool2)
+            lines++;
+	for (int i = 0; i < product->hops.size(); i++) {
+	    if (product->hops.at(i).h_useat == HOP_USEAT_WHIRLPOOL)
+		lines++;
+	}
+	if ((y + (lines * 20)) > painter.device()->height()) {
+            printer->newPage();
+            printHeader(&painter);
+            y = 120;
+        } else {
+            y += 20;
+        }
+	if ((product->brew_whirlpool9 + product->brew_whirlpool7 + product->brew_whirlpool6 + product->brew_whirlpool2) > 0) {
+	    checkHeader(&painter, &y, tr("Whirlpool(s) and cooling"));
+	    if (product->brew_whirlpool9 > 0)
+		checkLine(&painter, &y, QString(tr("Wirlpool for %1 minutes. Keep temp above 85°C")).arg(product->brew_whirlpool9, 1, 'f', 0));
+	    if (product->brew_whirlpool7 > 0)
+                checkLine(&painter, &y, QString(tr("Wirlpool for %1 minutes. Keep temp between 72 and 79°C")).arg(product->brew_whirlpool7, 1, 'f', 0));
+	    if (product->brew_whirlpool6 > 0)
+                checkLine(&painter, &y, QString(tr("Wirlpool for %1 minutes. Keep temp between 60 and 66°C")).arg(product->brew_whirlpool6, 1, 'f', 0));
+	    for (int i = 0; i < product->hops.size(); i++) {
+            	if (product->hops.at(i).h_useat == HOP_USEAT_WHIRLPOOL)
+		    checkLine(&painter, &y, QString(tr("%1 gr `%2` for %3 minutes in the whirlpool"))
+			.arg(product->hops.at(i).h_amount * 1000 * factor, 1, 'f', 1).arg(product->hops.at(i).h_name)
+			.arg(product->hops.at(i).h_time));
+	    }
+	    checkLine(&painter, &y, QString(tr("Cool to %1°C")).arg(product->brew_cooling_to, 1, 'f', 1));
+	    if (product->brew_whirlpool2 > 0)
+                checkLine(&painter, &y, QString(tr("Wirlpool for %1 minutes.")).arg(product->brew_whirlpool2, 1, 'f', 0));
+	} else {
+	    checkHeader(&painter, &y, tr("Cooling"));
+	    checkLine(&painter, &y, QString(tr("Cool to %1°C")).arg(product->brew_cooling_to, 1, 'f', 1));
+	}
+	checkLine(&painter, &y, QString(tr("Desinfect fermenter and pump and hoses if needed")));
+	checkInput(&painter, &y, QString(tr("Transfer wort to fermenter")), QString(tr("Liter")));
+	if (checkSplit(&painter, &y, 3))
+            factor = 1;
+
+	double climate = product->brew_cooling_to;
+    	lines = 3;
+    	for (int i = 0; i < product->yeasts.size(); i++) {
+	    if (product->yeasts.at(i).y_use == YEAST_USE_PRIMARY) {
+	    	lines++;
+	    	if (product->yeasts.at(i).y_type == YEAST_TYPES_KVEIK && (product->yeasts.at(i).y_pitch_temperature > 0))
+		    lines++;
+	    }
+    	}
+    	if (product->brew_aeration_type > 0)
+	    lines++;
+	if (product->brew_fermenter_extrawater > 0)
+	    lines++;
+    	if ((y + (lines * 20)) > painter.device()->height()) {
+            printer->newPage();
+            printHeader(&painter);
+            y = 120;
+        } else {
+            y += 20;
+        }
+	double dry = 0;
+	checkHeader(&painter, &y, tr("Yeast pitching and fermentation"));
+	for (int i = 0; i < product->yeasts.size(); i++) {
+            if (product->yeasts.at(i).y_use == YEAST_USE_PRIMARY) {
+		switch (product->yeasts.at(i).y_form) {
+		    case YEAST_FORMS_LIQUID:	checkLine(&painter, &y, QString(tr("%1 pack %2, `%3` yeast")).arg(product->yeasts.at(i).y_amount)
+							.arg(product->yeasts.at(i).y_product_id).arg(product->yeasts.at(i).y_name));
+			    			break;
+		    case YEAST_FORMS_DRY:	dry += product->yeasts.at(i).y_amount * 1000 * factor;
+		    case YEAST_FORMS_DRIED:	checkLine(&painter, &y, QString(tr("%1 gram %2, `%3` yeast"))
+							.arg(product->yeasts.at(i).y_amount * 1000 * factor, 1, 'f', 1)
+							.arg(product->yeasts.at(i).y_product_id).arg(product->yeasts.at(i).y_name));
+						break;
+		    default:			checkLine(&painter, &y, QString(tr("%1 ml %2, `%3` yeast"))
+							.arg(product->yeasts.at(i).y_amount * 1000 * factor, 1, 'f', 0)
+							.arg(product->yeasts.at(i).y_product_id).arg(product->yeasts.at(i).y_name));
+						break;
+		}
+		if (product->yeasts.at(i).y_type == YEAST_TYPES_KVEIK && (product->yeasts.at(i).y_pitch_temperature > 0)) {
+		    checkLine(&painter, &y, QString(tr("Pitch yeast at %1°C")).arg(product->yeasts.at(i).y_pitch_temperature, 1, 'f', 1));
+		    climate = product->yeasts.at(i).y_pitch_temperature;
+		}
+	    }
+	}
+	if (dry > 0) {
+	    checkLine(&painter, &y, QString(tr("Pitch yeast dry into the wort")));
+	} else {
+	    if (product->starter_enable && product->prop_volume[0])
+		checkLine(&painter, &y, QString(tr("Add decanted yeast starter")));
+	    else
+		checkLine(&painter, &y, QString(tr("Add the yeast")));
+	}
+	if (product->brew_fermenter_extrawater)
+	    checkLine(&painter, &y, QString(tr("Add %1 liter water in the fermenter")).arg(product->brew_fermenter_extrawater * factor, 1, 'f', 1));
+	if (product->brew_aeration_type > 0)
+	    checkLine(&painter, &y, QString(tr("Aerate %1 minutes with %2")).arg(product->brew_aeration_time)
+		.arg((product->brew_aeration_type == 1) ? "air":"oxygen"));
+	checkLine(&painter, &y, QString(tr("Set fermentation start temperature to %1°C")).arg(climate, 1, 'f', 1));
+	checkLine(&painter, &y, QString(tr("Start fermentation")));
+
+	/* During primary fermentation */
+	lines = 0;
+	for (int i = 0; i < product->fermentables.size(); i++)
+	    if (product->fermentables.at(i).f_added == FERMENTABLE_ADDED_FERMENTATION)
+		lines++;
+	for (int i = 0; i < product->miscs.size(); i++)
+	    if (product->miscs.at(i).m_use_use == MISC_USES_PRIMARY)
+		lines++;
+	if (lines) {
+	    if ((y + 20 + (lines * 20)) > painter.device()->height()) {
+            	printer->newPage();
+            	printHeader(&painter);
+            	y = 120;
+            } else {
+            	y += 20;
+            }
+	    checkHeader(&painter, &y, tr("Primary fermentation"));
+	    for (int i = 0; i < product->fermentables.size(); i++) {
+		if (product->fermentables.at(i).f_added == FERMENTABLE_ADDED_FERMENTATION)
+		    checkLine(&painter, &y, QString(tr("Add %1 kg `%2` on day 3 or 4")).arg(product->fermentables.at(i).f_amount * factor, 1, 'f', 3)
+			.arg(product->fermentables.at(i).f_name));
+	    }
+	    for (int i = 0; i < product->miscs.size(); i++) {
+		if (product->miscs.at(i).m_use_use == MISC_USES_PRIMARY) {
+		    QString unit = (product->miscs.at(i).m_amount_is_weight) ? "gr":"ml";
+		    checkLine(&painter, &y, QString(tr("Add %1 %2 `%3` on day 3 or 4")).arg(product->miscs.at(i).m_amount * factor, 1, 'f', 3)
+			.arg(unit).arg(product->miscs.at(i).m_name));
+		}
+	    }
+	}
+	if (checkSplit(&painter, &y, 4))
+            factor = 1;
+
+	/* During secondary fermentation, yeast */
+	lines = 0;
+	for (int i = 0; i < product->yeasts.size(); i++) {
+	    if (product->yeasts.at(i).y_use == YEAST_USE_SECONDARY)
+		lines++;
+	    if (product->yeasts.at(i).y_harvest_time > 0)
+		lines++;
+	}
+	if (lines) {
+	    if ((y + 20 + (lines * 20)) > painter.device()->height()) {
+                printer->newPage();
+                printHeader(&painter);
+                y = 120;
+            } else {
+                y += 20;
+            }
+            checkHeader(&painter, &y, tr("Secondary fermentation"));
+	    for (int i = 0; i < product->yeasts.size(); i++) {
+		if (product->yeasts.at(i).y_use == YEAST_USE_SECONDARY) {
+		    if (product->yeasts.at(i).y_form == YEAST_FORMS_LIQUID) {
+			checkLine(&painter, &y, QString(tr("Add %1 pack %2, `%3` yeast (with starter if needed)"))
+			    .arg(product->yeasts.at(i).y_amount).arg(product->yeasts.at(i).y_product_id).arg(product->yeasts.at(i).y_name));
+		    } else if (product->yeasts.at(i).y_form == YEAST_FORMS_DRY) {
+			checkLine(&painter, &y, QString(tr("Add %1 gram %2, `%3`")).arg(product->yeasts.at(i).y_amount * 1000 * factor, 1, 'f', 1)
+			    .arg(product->yeasts.at(i).y_product_id).arg(product->yeasts.at(i).y_name));
+		    } else {
+			checkLine(&painter, &y, QString(tr("Add %1 gram %2, `%3` yeast (with starter if needed)"))
+			    .arg(product->yeasts.at(i).y_amount * 1000 * factor, 1, 'f', 1)
+			    .arg(product->yeasts.at(i).y_product_id).arg(product->yeasts.at(i).y_name));
+		    }
+		}
+		if (product->yeasts.at(i).y_harvest_time > 0) {
+		    checkLine(&painter, &y, QString(tr("After %1 hours harvest yeast from the %2")).arg(product->yeasts.at(i).y_harvest_time)
+			    .arg((product->yeasts.at(i).y_harvest_top > 0) ? "top":"bottom"));
+		}
+	    }
+	}
+	if (checkSplit(&painter, &y, 5))
+            factor = 1;
+
+	/* During tertiary fermentation */
+        lines = 0;
+	for (int i = 0; i < product->fermentables.size(); i++)
+            if (product->fermentables.at(i).f_added == FERMENTABLE_ADDED_LAGERING)
+                lines++;
+	for (int i = 0; i < product->hops.size(); i++)
+	    if (product->hops.at(i).h_useat == HOP_USEAT_DRY_HOP)
+		lines++;
+        for (int i = 0; i < product->miscs.size(); i++)
+            if (product->miscs.at(i).m_use_use == MISC_USES_SECONDARY)
+                lines++;
+        for (int i = 0; i < product->yeasts.size(); i++) {
+            if (product->yeasts.at(i).y_use == YEAST_USE_TERTIARY)
+                lines++;
+        }
+        if (lines) {
+            if ((y + 20 + (lines * 20)) > painter.device()->height()) {
+                printer->newPage();
+                printHeader(&painter);
+                y = 120;
+            } else {
+                y += 20;
+            }
+            checkHeader(&painter, &y, tr("Tertiary fermentation"));
+	    for (int i = 0; i < product->fermentables.size(); i++) {
+                if (product->fermentables.at(i).f_added == FERMENTABLE_ADDED_LAGERING)
+                    checkLine(&painter, &y, QString(tr("Add %1 kg `%2`")).arg(product->fermentables.at(i).f_amount * factor, 1, 'f', 3)
+                        .arg(product->fermentables.at(i).f_name));
+            }
+	    for (int i = 0; i < product->hops.size(); i++) {
+		if (product->hops.at(i).h_useat == HOP_USEAT_DRY_HOP) {
+		    checkLine(&painter, &y, QString(tr("Add %1 gram `%2` for %3 days")).arg(product->hops.at(i).h_amount * 1000 * factor, 1, 'f', 1)
+			.arg(product->hops.at(i).h_name).arg(product->hops.at(i).h_time / 1440));
+		}
+	    }
+	    for (int i = 0; i < product->yeasts.size(); i++) {
+	    	if (product->yeasts.at(i).y_use == YEAST_USE_TERTIARY) {
+                    if (product->yeasts.at(i).y_form == YEAST_FORMS_LIQUID) {
+                        checkLine(&painter, &y, QString(tr("Add %1 pack %2, `%3` yeast (with starter if needed)"))
+                            .arg(product->yeasts.at(i).y_amount).arg(product->yeasts.at(i).y_product_id).arg(product->yeasts.at(i).y_name));
+                    } else if (product->yeasts.at(i).y_form == YEAST_FORMS_DRY) {
+                        checkLine(&painter, &y, QString(tr("Add %1 gram %2, `%3`")).arg(product->yeasts.at(i).y_amount * 1000 * factor, 1, 'f', 1)
+                            .arg(product->yeasts.at(i).y_product_id).arg(product->yeasts.at(i).y_name));
+                    } else {
+                        checkLine(&painter, &y, QString(tr("Add %1 ml %2, `%3` yeast (with starter if needed)"))
+                            .arg(product->yeasts.at(i).y_amount * 1000 * factor, 1, 'f', 1)
+                            .arg(product->yeasts.at(i).y_product_id).arg(product->yeasts.at(i).y_name));
+                    }
+                }
+	    }
+            for (int i = 0; i < product->miscs.size(); i++) {
+                if (product->miscs.at(i).m_use_use == MISC_USES_SECONDARY) {
+                    QString unit = (product->miscs.at(i).m_amount_is_weight) ? "gr":"ml";
+                    checkLine(&painter, &y, QString(tr("Add %1 %2 `%3` for %4 days")).arg(product->miscs.at(i).m_amount * factor, 1, 'f', 3)
+                        .arg(unit).arg(product->miscs.at(i).m_name).arg(product->miscs.at(i).m_time / 1440));
+                }
+            }
+	}
+	if (checkSplit(&painter, &y, 6))
+            factor = 1;
+
+	/* During packaging */
+	lines = 0;
+	for (int i = 0; i < product->fermentables.size(); i++)
+            if (product->fermentables.at(i).f_added >= FERMENTABLE_ADDED_BOTTLE)
+                lines++;
+        for (int i = 0; i < product->miscs.size(); i++)
+            if (product->miscs.at(i).m_use_use == MISC_USES_BOTTLING)
+                lines++;
+        for (int i = 0; i < product->yeasts.size(); i++)
+            if (product->yeasts.at(i).y_use == YEAST_USE_BOTTLE)
+                lines++;
+	if (lines) {
+            if ((y + 20 + (lines * 20)) > painter.device()->height()) {
+                printer->newPage();
+                printHeader(&painter);
+                y = 120;
+            } else {
+                y += 20;
+            }
+            checkHeader(&painter, &y, tr("Packaging"));
+            for (int i = 0; i < product->fermentables.size(); i++) {
+                if (product->fermentables.at(i).f_added == FERMENTABLE_ADDED_BOTTLE)
+                    checkLine(&painter, &y, QString(tr("Bottling add %1 kg `%2` with %3 liter water"))
+			.arg(product->fermentables.at(i).f_amount * factor, 1, 'f', 3)
+                        .arg(product->fermentables.at(i).f_name).arg(product->bottle_priming_water * factor, 1, 'f', 3));
+		if (product->fermentables.at(i).f_added == FERMENTABLE_ADDED_KEGS)
+                    checkLine(&painter, &y, QString(tr("Kegging add %1 kg `%2` with %3 liter water"))
+			.arg(product->fermentables.at(i).f_amount * factor, 1, 'f', 3)
+                        .arg(product->fermentables.at(i).f_name).arg(product->keg_priming_water * factor, 1, 'f', 3));
+            }
+	    for (int i = 0; i < product->yeasts.size(); i++) {
+            	if (product->yeasts.at(i).y_use == YEAST_USE_BOTTLE) {
+		    if (product->yeasts.at(i).y_form == YEAST_FORMS_LIQUID) {
+                        checkLine(&painter, &y, QString(tr("Add %1, `%2` as bottle yeast"))
+                            .arg(product->yeasts.at(i).y_product_id).arg(product->yeasts.at(i).y_name));
+                    } else if (product->yeasts.at(i).y_form == YEAST_FORMS_DRY) {
+                        checkLine(&painter, &y, QString(tr("Add %1 gram %2, `%3` as bottle yeast"))
+			    .arg(product->yeasts.at(i).y_amount * 1000 * factor, 1, 'f', 1)
+                            .arg(product->yeasts.at(i).y_product_id).arg(product->yeasts.at(i).y_name));
+                    } else {
+                        checkLine(&painter, &y, QString(tr("Add %1 ml %2, `%3` as bottle yeast"))
+                            .arg(product->yeasts.at(i).y_amount * 1000 * factor, 1, 'f', 1)
+                            .arg(product->yeasts.at(i).y_product_id).arg(product->yeasts.at(i).y_name));
+                    }
+		}
+	    }
+	    for (int i = 0; i < product->miscs.size(); i++) {
+                if (product->miscs.at(i).m_use_use == MISC_USES_BOTTLING) {
+                    QString unit = (product->miscs.at(i).m_amount_is_weight) ? "gr":"ml";
+                    checkLine(&painter, &y, QString(tr("Add %1 %2 `%3` during bottling")).arg(product->miscs.at(i).m_amount * factor, 1, 'f', 3)
+                        .arg(unit).arg(product->miscs.at(i).m_name));
+                }
+            }
+	}
     }
 
     painter.end();
 }
 
 
+void PrinterDialog::checkHeader(QPainter *painter, qreal *y, QString text)
+{
+    painter->setFont(QFont("Arial", 10, QFont::Bold));
+    painter->setPen(Qt::black);
+    painter->drawText(30, *y, 700, 20, Qt::AlignLeft, text);
+    painter->setFont(QFont("Arial", 10, QFont::Normal));
+    *y += 20;
+}
+
+
+/*
+ * Draw a checkbox and text.
+ */
+void PrinterDialog::checkLine(QPainter *painter, qreal *y, QString text)
+{
+    painter->drawRect( 4, *y, 16, 16);
+    painter->drawText(30, *y,650, 20, Qt::AlignLeft, text);
+    *y += 20;
+}
+
+
+void PrinterDialog::checkInput(QPainter *painter, qreal *y, QString text, QString prompt)
+{
+    if (text != "") {
+    	painter->drawRect( 4, *y, 16, 16);
+    	painter->drawText(30, *y,500, 20, Qt::AlignLeft, text);
+    }
+    painter->drawText(515, *y, 150, 20, Qt::AlignRight, QString(tr("Measured:")) + QString(" _________"));
+    painter->drawText(675, *y,  60, 20, Qt::AlignLeft, prompt);
+    *y += 20;
+}
+
+
+bool PrinterDialog::checkSplit(QPainter *painter, qreal *y, int moment)
+{
+    if (product->divide_type && product->divide_type == moment) {
+	*y += 20;
+	painter->setFont(QFont("Helvetica", 14, QFont::Bold));
+	painter->drawText(0, *y, 735, 20, Qt::AlignCenter, QString(tr("%1 split the batch here!")).arg(prod_split[product->divide_type]));
+    	painter->setFont(QFont("Arial", 10, QFont::Normal));
+	*y += 26;
+	return true;
+    }
+    return false;
+}
+
+
 QString PrinterDialog::strDiff(double v1, double v2, int decimals, QString suffix)
 {
     return QString("%1%2%3").arg((v2 > v1) ? "+":"").arg(v2 - v1, 1, 'f', decimals).arg(suffix);
@@ -1563,7 +2155,7 @@
 	painter->drawText(120, 0,  500, 40, Qt::AlignLeft, tr("Yeastbank") + " " + my_brewery_name);
     } else if (p_job == PR_RECIPE) {
 	painter->drawText(120, 0,  500, 40, Qt::AlignLeft, recipe->name);
-    } else if (p_job == PR_PRODUCT) {
+    } else if (p_job == PR_PRODUCT || p_job == PR_CHECKLIST) {
         painter->drawText(120, 0,  500, 40, Qt::AlignLeft, product->code + "  " + product->name);
     } else {
         painter->drawText(120, 0,  500, 40, Qt::AlignLeft, "?? " + my_brewery_name);
@@ -1576,7 +2168,7 @@
 	painter->drawText(120,55,  80, 20, Qt::AlignLeft, tr("Beer style"));
     	painter->drawText(200,55, 400, 20, Qt::AlignLeft, ": " + recipe->st_name);
     }
-    if (p_job == PR_PRODUCT) {
+    if (p_job == PR_PRODUCT || p_job == PR_CHECKLIST) {
         painter->drawText(120,55,  80, 20, Qt::AlignLeft, tr("Beer style"));
         painter->drawText(200,55, 400, 20, Qt::AlignLeft, ": " + product->st_name);
     }
--- a/src/PrinterDialog.h	Sat May 28 21:08:09 2022 +0200
+++ b/src/PrinterDialog.h	Mon May 30 21:44:40 2022 +0200
@@ -6,7 +6,7 @@
 #include <QPainter>
 
 
-enum JobType { PR_SUPPLIES, PR_YEASTBANK, PR_RECIPE, PR_PRODUCT };
+enum JobType { PR_SUPPLIES, PR_YEASTBANK, PR_RECIPE, PR_PRODUCT, PR_CHECKLIST };
 
 namespace Ui {
 class PrinterDialog;
@@ -39,6 +39,10 @@
     void printHeader(QPainter *painter);
     QString strDensity(double density);
     QString strDiff(double v1, double v2, int decimals, QString suffix);
+    void checkHeader(QPainter *painter, qreal *y, QString text);
+    void checkLine(QPainter *painter, qreal *y, QString text);
+    void checkInput(QPainter *painter, qreal *y, QString text, QString prompt);
+    bool checkSplit(QPainter *painter, qreal *y, int moment);
 };
 
 #endif

mercurial