/// /// Copyright (c) 2023 Sensus Slovensko a.s. /// using System; using System.Collections.Generic; using System.Drawing; using System.Linq; using System.Windows.Forms; using log4net; using Common; using Results.Entities; using TBF.Rig.GenericDevices; using TBF.Resources; using System.Threading; using System.IO; using static System.Net.Mime.MediaTypeNames; namespace TBF.Rig.DataEntry.Uni { public partial class TestStartEndForm : Form, GenericDevices.IHasCompleted { private static readonly ILog log = LogManager.GetLogger(typeof(TestStartEndForm)); const int PicSize = 480; /// /// Static members and constructor /// static int wmNr0b; static Rotation imageRotation; static bool[] isZoomed; static Point[] zoomCenterLoc; /// static TestStartEndForm() { wmNr0b = 0; //imageRotation = new Rotation[40]; isZoomed = new bool[40]; zoomCenterLoc = new Point[40]; } /// Arguments public readonly IList waterMeters; readonly IRegReader[] regReaders; readonly int lineSize; readonly FontSz sz; readonly bool isStartBoxAlwaysEn; readonly bool isLrOrder; /// true = controls obtain focus after ENTER key in left-right order, false = top-down order readonly IList colItems; readonly bool isCompound; readonly string formCloseKeys; public readonly string[] WMStartStateStr; readonly bool isCameraPicture; /// true = camera picture displayed on the left side readonly string[] startImages; readonly string[] endImages; readonly OcrVidi ocrVidi; readonly string ocrMessage; readonly string streamName; readonly double refVolume; readonly double warningLimLo; /// limit to display exclamation mark, typically 2x errLimLo readonly double warningLimHi; /// limit to display exclamation mark, typically 2x errLimHi /// Derived from arguments in the constructor readonly bool isEnd; readonly Tst mode; /// Start, End or Deferred readonly WaterMeter firstValidWM; readonly int wmsCount; readonly int linesCount; readonly bool fixedErrorLimits; /// false in case of calculated error limits /// To be retrieved after the form closes public double[] WMStartState; public double[] WMEndState; public Unit VolumeUnit; int startStateColumn; int startStateColumnAux; int endStateColumn; int endStateColumnAux; /// Size and layout related values readonly int meterHeight; readonly int meterWidth; readonly int margin; readonly int spacing; readonly int hdrHeight; readonly int labelWid; readonly int checkBoxWid; /// UI elements readonly Label[] labels; /// Labels for water meter numbers readonly TextBox[,] textBoxes; /// Text boxes for values in columns readonly Label[] exclamations; /// Picture box related PictureBox pictureBox1; Label imageProcessingProgressLabel; ProgressBar imageProcessingProgressBar; string lastImageName; int lastWmNr1; Bitmap lastImage; /// non-zoomed image Bitmap zoomedImage; Thread imageProcessingThread; /// Thread to process images public delegate void OcrTextObtainedDelegate(int ix, bool isEnd, string text); OcrTextObtainedDelegate ocrTextObtained; readonly LocalSettings ls; bool isHandlersEnabled; /// Initially false, set to true after ComboBoxes are filled with data /// Set to 'true' when the form closes public bool Completed { get { return completed; } } bool completed; /// /// Parameterless constructor for common functionality /// public TestStartEndForm() { InitializeComponent(); ControlBox = false; imageProcessingThread = null; completed = false; StartForceCloseHandler(); } /// /// Constructor /// /// Water meters /// Array of register readers /// Number of water meters in one (test bencch) line /// Form title /// Font size /// true = start box enabled at the end of a test /// true = controls obtain focus in left-right order, forls = top-down order /// String containing keys to close this form /// Water meter items displayed in columns (in the matrix in the main part of the form) /// true = Compound meters: StartStateAux and EndStateAux columns displayed /// Volume unit preset initially /// true = display camera pictures in this form /// Images before a standing start/stop test /// Images after a standing start/stop test /// Reference to OCR objejct /// Message to be displayed when OCR was not initialized properly /// OCR stream selection /// Information entered on test start /// Reference volume, it is used to verify the end state, show/hide exclamation if necessary /// Error limit low, it is used to verify the end state, show/hide exclamation if necessary /// Error limit high, it is used to verify the end state, show/hide exclamation if necessary public TestStartEndForm(IList waterMeters, IRegReader[] regReaders, int _lineSize, string title, FontSz sz, bool isStartBoxAlwaysEn, bool isLrOrder, string formCloseKeys, IList colItems, bool isCompound, Unit initialVolumeUnit, bool isCameraPicture, string[] startImages, string[] endImages, OcrVidi ocrVidi, string ocrMessage, string streamName, string[] wmStartStateStr = null, double refVolume = 0, double errLimLo = 0, double errLimHi = 0) : this() { this.waterMeters = waterMeters; this.regReaders = regReaders; this.lineSize = Math.Min(waterMeters.Count, _lineSize); this.Text = !string.IsNullOrEmpty(title) ? title : string.Empty; this.sz = sz; this.isStartBoxAlwaysEn = isStartBoxAlwaysEn; this.isLrOrder = isLrOrder; this.formCloseKeys = !string.IsNullOrEmpty(formCloseKeys) ? formCloseKeys : string.Empty; this.colItems = colItems; this.isCompound = isCompound; this.VolumeUnit = initialVolumeUnit; this.isCameraPicture = isCameraPicture; this.startImages = startImages; this.endImages = endImages; this.ocrVidi = ocrVidi; this.ocrMessage = ocrMessage; this.streamName = streamName; this.WMStartStateStr = wmStartStateStr; this.refVolume = refVolume; this.warningLimLo = 2 * errLimLo; this.warningLimHi = 2 * errLimHi; if (waterMeters == null || regReaders == null || (wmStartStateStr != null && wmStartStateStr.Length != (isCompound ? 2 : 1) * waterMeters.Count)) { throw new Exception("Invalid argument"); } ls = Program.LocalSettings; /// Readonly variables derived from arguments isEnd = (wmStartStateStr != null); mode = isEnd ? Tst.End : Tst.Start; wmsCount = waterMeters.Count; if (WMStartStateStr == null) WMStartStateStr = new string[(isCompound ? 2 : 1) * wmsCount]; fixedErrorLimits = (errLimHi > errLimLo); linesCount = (wmsCount + Math.Max(1, lineSize) - 1) / Math.Max(1, lineSize); firstValidWM = waterMeters.FirstOrDefault(x => (x != null && !x.Disabled)); WMStartState = new double[(isCompound ? 2 : 1) * wmsCount]; WMEndState = new double[(isCompound ? 2 : 1) * wmsCount]; /// unitComboBox.Text = VolumeUnit.ToDescription(); for (Unit u = 0; u < Unit.Count; u++) { if (Units.IsVolume(u)) unitComboBox.Items.Add(u.ToDescription()); } labels = new Label[wmsCount]; textBoxes = new TextBox[colItems.Count, wmsCount]; exclamations = new Label[wmsCount]; /// Layout related readonly variables derived from argument 'sz' Font font; Font exclamationFont; switch (sz) { case FontSz.S: font = new Font("Verdana", 12, FontStyle.Regular); //exclamationFont = new Font("Verdana", 24F, FontStyle.Bold, GraphicsUnit.Point, ((byte)(238))); exclamationFont = new Font("Verdana", 18, FontStyle.Bold); meterHeight = 26; /// Height of a ComboBox control margin = 20; spacing = 8; hdrHeight = 160; labelWid = 31; checkBoxWid = 23; break; default: case FontSz.M: font = new Font("Verdana", 14, FontStyle.Regular); exclamationFont = new Font("Verdana", 20, FontStyle.Bold); meterHeight = 31; /// Height of a ComboBox control margin = 25; spacing = 10; hdrHeight = 160; labelWid = 34; checkBoxWid = 23; break; case FontSz.L: font = new Font("Verdana", 18, FontStyle.Regular); exclamationFont = new Font("Verdana", 24, FontStyle.Bold); meterHeight = 37; /// Height of a ComboBox control margin = 30; spacing = 12; hdrHeight = 160; labelWid = 40; checkBoxWid = 23; break; } meterWidth = labelWid + checkBoxWid + spacing; foreach (var ci in colItems) { meterWidth += ci.Width + spacing; } int tabIndex = 1; /// /// Table /// int columnsLeft = 0; if (isCameraPicture) { columnsLeft = PicSize + 10; largeTextBox.Left += columnsLeft; largeExclamationLabel.Left += columnsLeft; /// Picture box pictureBox1 = new PictureBox { Location = new System.Drawing.Point(4, 5), Name = "pictureBox1", Size = new System.Drawing.Size(PicSize, PicSize), SizeMode = System.Windows.Forms.PictureBoxSizeMode.StretchImage, TabIndex = tabIndex++, TabStop = false, }; pictureBox1.Click += new System.EventHandler(pictureBox1_Click); Controls.Add(pictureBox1); /// Rotate image button var rotateImageButton = new Button { Font = new System.Drawing.Font("Arial", 27.75F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(238))), Location = new System.Drawing.Point(24, PicSize + 31), Name = "rotateImageButton", Size = new System.Drawing.Size(50, 50), TabIndex = tabIndex++, Text = "↺", UseVisualStyleBackColor = true, }; rotateImageButton.Click += new System.EventHandler(rotateImageButton_Click); Controls.Add(rotateImageButton); /// Image processing progress bar imageProcessingProgressBar = new ProgressBar { Location = new System.Drawing.Point(100, PicSize + 56), Name = "imageProcessingProgressBar", Size = new System.Drawing.Size(246, 23), TabIndex = tabIndex++, Visible = false, }; Controls.Add(imageProcessingProgressBar); /// Image processing progress label imageProcessingProgressLabel = new Label { AutoSize = true, Font = new System.Drawing.Font("Verdana", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238))), Location = new System.Drawing.Point(100, PicSize + 31), Name = "imageProcessingProgressLabel", Size = new System.Drawing.Size(249, 18), TabIndex = tabIndex++, Text = "Progress of image processing", Visible = false, }; Controls.Add(imageProcessingProgressLabel); } int columnsTop = hdrHeight; for (int j = 0; j < linesCount; j++) { /// Captions int capX = columnsLeft + margin + j * (meterWidth + margin / 2) + labelWid + spacing; int capY = columnsTop - meterHeight; for (int k = 0; k < colItems.Count; k++) { int labelWidth = Convert.ToInt32(Graphics.FromImage(new Bitmap(1, 1)).MeasureString(colItems[k].Caption, font).Width); var label = new Label { Text = colItems[k].Caption, Location = new Point(capX, capY), Size = new Size(labelWidth + spacing, meterHeight), Font = font, TabIndex = tabIndex++, Parent = this, }; capX += colItems[k].Width + spacing; Controls.Add(label); } /// Water meters for (int i = 0; i < lineSize; i++) { int wmPos0 = i + lineSize * j; if (wmPos0 > wmsCount) continue; int left = columnsLeft + margin + j * (meterWidth + margin / 2); int top = columnsTop + i * (meterHeight + spacing); var label = new Label { Text = (wmPos0 + 1).ToString(), Location = new Point(left, top), Size = new Size(labelWid, meterHeight), Font = font, TextAlign = ContentAlignment.MiddleLeft, TabIndex = tabIndex++, Parent = this, }; left += labelWid + spacing / 2; labels[wmPos0] = label; Controls.Add(label); /// Columns for (int k = 0; k < colItems.Count; k++) { DEItem ci = colItems[k]; var textBox = new TextBox { Name = string.Format("{0}~{1}", k, wmPos0), Location = new Point(left, top), Size = new Size(ci.Width, meterHeight), Enabled = (ci.Action != Ac.LoadReadOnly && !waterMeters[wmPos0].Disabled), Font = font, TabIndex = tabIndex++, Parent = this, }; textBox.AcceptsTabChanged += new System.EventHandler(textBox_AcceptsTabChanged); textBox.TextChanged += new System.EventHandler(textBox_TextChanged); textBox.KeyPress += new System.Windows.Forms.KeyPressEventHandler(textBox_KeyPress); textBox.MouseClick += new System.Windows.Forms.MouseEventHandler(textBox_MouseClick); textBox.MouseDown += new System.Windows.Forms.MouseEventHandler(textBox_MouseDown); left += ci.Width + spacing; textBoxes[k, wmPos0] = textBox; Controls.Add(textBox); } var exclamation = new Label { AutoSize = true, Font = exclamationFont, ForeColor = System.Drawing.Color.Red, Location = new Point(left, top), Parent = this, TabIndex = tabIndex++, Text = "!", Visible = false, }; exclamations[wmPos0] = exclamation; Controls.Add(exclamation); } } /// /// Adjust window size /// Rectangle screenRectangle = this.RectangleToScreen(this.ClientRectangle); int titleBarHeight = screenRectangle.Top - this.Top; Size = new Size(columnsLeft + Math.Max(meterWidth * linesCount + margin * (linesCount + 1), 1040), titleBarHeight + Math.Max(columnsTop + lineSize * (meterHeight + spacing) + margin, isCameraPicture ? 590 : 0)); Localize(); InitializeBoxes(); /// Set focus to the first enabled ComboBox } private void TestStartEndForm_Load(object sender, EventArgs e) { if (!string.IsNullOrEmpty(ocrMessage)) { MessageBox.Show(ocrMessage, Strings.Warning, MessageBoxButtons.OK, MessageBoxIcon.Exclamation); } int imagesCount = ImagesToProcessCount(); log.WarnFormat("Images to process count = {0}", imagesCount); /// if (imagesCount > 0 && ocrVidi != null) { log.WarnFormat("Starting image processing thread with OcrVidi={0} and OcrStream={1}", ocrVidi, streamName); /// Delegate for a thread->form communication ocrTextObtained = new OcrTextObtainedDelegate(OnOcrTextObtained); /// Show and initalize the progress bar imageProcessingProgressLabel.Visible = true; imageProcessingProgressBar.Visible = true; imageProcessingProgressBar.Minimum = 0; imageProcessingProgressBar.Value = 0; imageProcessingProgressBar.Maximum = imagesCount; /// Start the image processing thread imageProcessingThread = new Thread(new ThreadStart(ImageProcessingThread)); imageProcessingThread.Start(); } } private void TestStartEndForm_FormClosed(object sender, FormClosedEventArgs e) { if (imageProcessingThread != null && imageProcessingThread.IsAlive) { imageProcessingThread.Join(2000); } } void Localize() { okButton.Text = Strings.OkBtnText; } /// /// Initialize combo boxes state defined by related 'Action'. /// Clear check boxes. /// void InitializeBoxes() { isHandlersEnabled = false; /// Table startStateColumn = -1; startStateColumnAux = -1; endStateColumn = -1; endStateColumnAux = -1; for (int k = 0; k < colItems.Count; k++) { if (colItems[k].Content == Ct.StartState) { startStateColumn = k; for (int i = 0; i < wmsCount; i++) { int rrIx = (isCompound ? 2 : 1) * i; textBoxes[k, i].Text = (!isEnd || waterMeters[i] == null || waterMeters[i].Disabled || i >= regReaders.Length || regReaders[i] == null || string.IsNullOrEmpty(WMStartStateStr[i])) ? string.Empty : WMStartStateStr[rrIx]; textBoxes[k, i].Enabled = (isStartBoxAlwaysEn || !isEnd) && waterMeters[i] != null && !waterMeters[i].Disabled && rrIx < regReaders.Length && regReaders[rrIx] != null; } } else if (colItems[k].Content == Ct.StartStateAux) { startStateColumnAux = k; for (int i = 0; i < wmsCount; i++) { int rrIx = 2 * i + 1; textBoxes[k, i].Text = (!isEnd || !isCompound || waterMeters[i] == null || waterMeters[i].Disabled || i >= regReaders.Length || regReaders[i] == null || string.IsNullOrEmpty(WMStartStateStr[i])) ? string.Empty : WMStartStateStr[rrIx]; textBoxes[k, i].Enabled = isCompound && (isStartBoxAlwaysEn || !isEnd) && waterMeters[i] != null && !waterMeters[i].Disabled && rrIx < regReaders.Length && regReaders[rrIx] != null; } } else if (colItems[k].Content == Ct.EndState) { endStateColumn = k; for (int i = 0; i < wmsCount; i++) { int rrIx = (isCompound ? 2 : 1) * i; textBoxes[k, i].Text = string.Empty; textBoxes[k, i].Enabled = isEnd && waterMeters[i] != null && !waterMeters[i].Disabled && rrIx < regReaders.Length && regReaders[rrIx] != null; } } else if (colItems[k].Content == Ct.EndStateAux) { endStateColumn = k; for (int i = 0; i < wmsCount; i++) { int rrIx = 2 * i + 1; textBoxes[k, i].Text = string.Empty; textBoxes[k, i].Enabled = isEnd && isCompound && waterMeters[i] != null && !waterMeters[i].Disabled && rrIx < regReaders.Length && regReaders[rrIx] != null; } } else { switch (colItems[k].Action) { default: case Ac.Clear: case Ac.HistoryLast: for (int i = 0; i < wmsCount; i++) textBoxes[k, i].Text = string.Empty; break; case Ac.Load: case Ac.LoadReadOnly: for (int i = 0; i < wmsCount; i++) { textBoxes[k, i].Text = DEUtils.GetContent(colItems[k].Content, waterMeters[i]); } break; } } } if (isEnd && isStartBoxAlwaysEn) { int k = 0; int j = 0; if (!FindNextEnabledTextBox(textBoxes, ref k, ref j, isLrOrder, true)) { ActiveControl = textBoxes[k, j]; } } isHandlersEnabled = true; } /// /// When one combo box is updated using drop-down menu, all combo boxes /// are updated by this function. /// void UpdateWMsFromBoxes() { for (int k = 0; k < colItems.Count; k++) { for (int i = 0; i < wmsCount; i++) { int rrIx = (isCompound ? 2 : 1) * i; double volume; if (colItems[k].Content == Ct.StartState) { if (textBoxes[k, i].Enabled && Utils.TryParseUDouble(textBoxes[k, i].Text, out volume)) { WMStartStateStr[rrIx] = textBoxes[k, i].Text; WMStartState[rrIx] = Units.ConvertFrom(VolumeUnit, volume); } } else if (colItems[k].Content == Ct.StartStateAux) { if (isCompound && textBoxes[k, i].Enabled && Utils.TryParseUDouble(textBoxes[k, i].Text, out volume)) { WMStartStateStr[rrIx + 1] = textBoxes[k, i].Text; WMStartState[rrIx + 1] = Units.ConvertFrom(VolumeUnit, volume); } } else if (colItems[k].Content == Ct.EndState) { if (textBoxes[k, i].Enabled && Utils.TryParseUDouble(textBoxes[k, i].Text, out volume)) { WMEndState[rrIx] = Units.ConvertFrom(VolumeUnit, volume); } } else if (colItems[k].Content == Ct.EndStateAux) { if (isCompound && textBoxes[k, i].Enabled && Utils.TryParseUDouble(textBoxes[k, i].Text, out volume)) { WMEndState[rrIx + 1] = Units.ConvertFrom(VolumeUnit, volume); } } else { /// Other value - store values to a water meter structure DEUtils.PutContent(colItems[k].Content, waterMeters[i], textBoxes[k, i].Text); } } } } int ImagesToProcessCount() { int count = 0; /// Count images for (int i = 0; i < wmsCount; i++) { int rrIx = (isCompound ? 2 : 1) * i; if (mode == Tst.Start || mode == Tst.Both) { if (startStateColumn >= 0 && textBoxes[startStateColumn, i].Enabled && startImages != null && startImages.Length > rrIx && !string.IsNullOrEmpty(startImages[rrIx])) { count++; } if (startStateColumnAux >= 0 && textBoxes[startStateColumnAux, i].Enabled && startImages != null && startImages.Length > rrIx + 1 && !string.IsNullOrEmpty(startImages[rrIx + 1])) { count++; } } if (mode == Tst.End || mode == Tst.Both) { if (endStateColumn >= 0 && textBoxes[endStateColumn, i].Enabled && endImages != null && endImages.Length > rrIx && !string.IsNullOrEmpty(endImages[rrIx])) { count++; } if (endStateColumnAux >= 0 && textBoxes[endStateColumnAux, i].Enabled && endImages != null && endImages.Length > rrIx + 1 && !string.IsNullOrEmpty(endImages[rrIx + 1])) { count++; } } } return count; } void ImageProcessingThread() { double denominator; /// derived from PulsesPerLtr, passed to OcrVidi string format; /// derived from PulsesPerLtr, passed to OcrVidi Thread.Sleep(2000); bool[] startImgProcessed = new bool[(isCompound ? 2 : 1) * wmsCount]; bool[] endImgProcessed = new bool[(isCompound ? 2 : 1) * wmsCount]; for (int attempt = 1; attempt <= 3; attempt++) { for (int i = 0; i < wmsCount; i++) { int rrIx = (isCompound ? 2 : 1) * i; double pulsesPerLtr = (regReaders != null && regReaders.Length > rrIx && regReaders[rrIx] != null) ? regReaders[rrIx].PulsesPerLtr : 1; double pulsesPerLtrAux = (regReaders != null && regReaders.Length > rrIx + 1 && regReaders[rrIx + 1] != null) ? regReaders[rrIx + 1].PulsesPerLtr : 1; if (mode == Tst.Start || mode == Tst.Both) { if (startStateColumn >= 0 && textBoxes[startStateColumn, i].Enabled && startImages != null && startImages.Length > rrIx && !string.IsNullOrEmpty(startImages[rrIx])) { if (!startImgProcessed[rrIx]) { try { /// Process a start image GetOcrParameters(pulsesPerLtr, out denominator, out format); var text = ocrVidi.Recognize(streamName, startImages[rrIx], denominator, format); /// OCR this.Invoke(ocrTextObtained, new object[] { rrIx, false, text }); startImgProcessed[rrIx] = true; } catch (Exception) { } } } if (startStateColumnAux >= 0 && textBoxes[startStateColumnAux, i].Enabled && startImages != null && startImages.Length > rrIx + 1 && !string.IsNullOrEmpty(startImages[rrIx + 1])) { if (!startImgProcessed[rrIx + 1]) { try { /// Process a start image GetOcrParameters(pulsesPerLtrAux, out denominator, out format); var text = ocrVidi.Recognize(streamName, startImages[rrIx + 1], denominator, format); /// OCR this.Invoke(ocrTextObtained, new object[] { rrIx + 1, false, text }); startImgProcessed[rrIx + 1] = true; } catch (Exception) { } } } } if (mode == Tst.End || mode == Tst.Both) { if (endStateColumn >= 0 && textBoxes[endStateColumn, i].Enabled && endImages != null && endImages.Length > rrIx && !string.IsNullOrEmpty(endImages[rrIx])) { if (!endImgProcessed[rrIx]) { try { /// Process an end image GetOcrParameters(pulsesPerLtr, out denominator, out format); var text = ocrVidi.Recognize(streamName, endImages[rrIx], denominator, format); /// OCR this.Invoke(ocrTextObtained, new object[] { rrIx, true, text }); endImgProcessed[rrIx] = true; } catch (Exception) { } } } if (endStateColumnAux >= 0 && textBoxes[endStateColumnAux, i].Enabled && endImages != null && endImages.Length > rrIx + 1 && !string.IsNullOrEmpty(endImages[rrIx + 1])) { if (!endImgProcessed[rrIx + 1]) { try { /// Process an end image GetOcrParameters(pulsesPerLtrAux, out denominator, out format); var text = ocrVidi.Recognize(streamName, endImages[rrIx + 1], denominator, format); /// OCR this.Invoke(ocrTextObtained, new object[] { rrIx + 1, true, text }); endImgProcessed[rrIx + 1] = true; } catch (Exception) { } } } } } } } void GetOcrParameters(double pulsesPerLtr, out double denominator, out string format) { if (pulsesPerLtr == 0.1) { denominator = 100; format = "F2"; } else if (pulsesPerLtr == 0.01) { denominator = 10; format = "F1"; } else if (pulsesPerLtr == 10) { denominator = 10000; format = "F4"; } else // if (pulsesPerLtr == 1) { denominator = 1000; format = "F3"; } } public void OnOcrTextObtained(int rrIx, bool _isEnd, string text) { if (!isCameraPicture) return; if (_isEnd) { if (endStateColumn>= 0 && textBoxes[endStateColumn, rrIx].Enabled) { textBoxes[endStateColumn, rrIx].Text = text; LoadImage(null, Tst.End, rrIx); } } else { if (startStateColumn >= 0 && textBoxes[startStateColumn, rrIx].Enabled) { textBoxes[endStateColumn, rrIx].Text = text; LoadImage(null, Tst.Start, rrIx); } } imageProcessingProgressBar.Value++; } private void okButton_Click(object sender, EventArgs eArgs) { UpdateWMsFromBoxes(); completed = true; Close(); } private void unitComboBox_TextChanged(object sender, EventArgs e) { unitComboBox_SelectedIndexChanged(sender, e); } private void unitComboBox_SelectedIndexChanged(object sender, EventArgs e) { var newUnit = Units.FromDescription(unitComboBox.Text); if (Units.IsVolume(newUnit)) { VolumeUnit = newUnit; if (!isHandlersEnabled) return; /// Update exclamation marks if (isEnd && startStateColumn >= 0 && endStateColumn >= 0) { for (int i = 0; i < wmsCount; i++) { double startVol, endVol, startVolAux, endVolAux; if (isCompound && startStateColumnAux >= 0 && endStateColumnAux >= 0) { /// Compound if (Utils.TryParseUDouble(textBoxes[startStateColumn, i].Text, out startVol) && Utils.TryParseUDouble(textBoxes[startStateColumnAux, i].Text, out startVolAux) && Utils.TryParseUDouble(textBoxes[endStateColumn, i].Text, out endVol) && Utils.TryParseUDouble(textBoxes[endStateColumnAux, i].Text, out endVolAux)) { double startState = Units.ConvertFrom(VolumeUnit, startVol + startVolAux); double endState = Units.ConvertFrom(VolumeUnit, endVol + endVolAux); double err = (refVolume != 0) ? 100.0 * (endState - startState - refVolume) / refVolume : 1000.0; exclamations[i].Visible = fixedErrorLimits ? ((err < warningLimLo) || (warningLimHi < err)) : ((err < -6) || (+6 < err)); } else { exclamations[i].Visible = !string.IsNullOrEmpty(textBoxes[endStateColumn, i].Text); } } else { /// Single if (Utils.TryParseUDouble(textBoxes[startStateColumn, i].Text, out startVol) && Utils.TryParseUDouble(textBoxes[endStateColumn, i].Text, out endVol)) { double startState = Units.ConvertFrom(VolumeUnit, startVol); double endState = Units.ConvertFrom(VolumeUnit, endVol); double err = (refVolume != 0) ? 100.0 * (endState - startState - refVolume) / refVolume : 1000.0; exclamations[i].Visible = fixedErrorLimits ? ((err < warningLimLo) || (warningLimHi < err)) : ((err < -6) || (+6 < err)); } else { exclamations[i].Visible = !string.IsNullOrEmpty(textBoxes[endStateColumn, i].Text); } } } largeTextBox.Text = string.Empty; largeExclamationLabel.Visible = false; } } } private void textBox_KeyPress(object sender, KeyPressEventArgs e) { if (!isHandlersEnabled) return; string[] kj = (sender as TextBox).Name.Split(new char[] { '~' }); int k = int.Parse(kj[0]); int j = int.Parse(kj[1]); /// Close the form if any 'form close key' was pressed if (!string.IsNullOrEmpty(formCloseKeys)) { /// Close the form if any 'close form key' was pressed for (int ix = 0; ix < formCloseKeys.Length; ix++) { if (e.KeyChar == formCloseKeys[ix]) { okButton_Click(sender, e); return; } } } if (e.KeyChar == '\r') { /// Change focus if (FindNextEnabledTextBox(textBoxes, ref k, ref j, isLrOrder)) { okButton.Focus(); } else { largeTextBox.Text = string.Format("{0}: {1}", j + 1, textBoxes[k, j].Text); largeExclamationLabel.Visible = false; if (isCameraPicture && (k == startStateColumn || k == startStateColumnAux || k == endStateColumn || k == endStateColumnAux)) { int rrIx = (isCompound ? 2 : 1) * j + ((k == startStateColumnAux || k == endStateColumnAux) ? 1 : 0); LoadImage(sender, (k == startStateColumn || k == startStateColumnAux) ? Tst.Start : Tst.End, rrIx); } textBoxes[k, j].Focus(); } } } /// Handle mouse clicks so that the large text box and the large exclamation mark are updated private void textBox_MouseClick(object sender, MouseEventArgs e) { textBox_TextChanged(sender, e); } private void textBox_TextChanged(object sender, EventArgs e) { if (!isHandlersEnabled) return; string[] kj = (sender as TextBox).Name.Split(new char[] { '~' }); int k = int.Parse(kj[0]); int j = int.Parse(kj[1]); largeTextBox.Text = string.Format("{0}: {1}", j + 1, textBoxes[k, j].Text); if (isEnd && (k == startStateColumn || k == startStateColumnAux || k == endStateColumn || k == endStateColumnAux)) { UpdateExclamation(j); } else { largeExclamationLabel.Visible = false; } } void UpdateExclamation(int j) { double startVol, startVolAux, endVol, endVolAux; if (isCompound && startStateColumn >= 0 && Utils.TryParseUDouble(textBoxes[startStateColumn, j].Text, out startVol) && startStateColumnAux >= 0 && Utils.TryParseUDouble(textBoxes[startStateColumnAux, j].Text, out startVolAux) && endStateColumn >= 0 && Utils.TryParseUDouble(textBoxes[endStateColumn, j].Text, out endVol) && endStateColumnAux >= 0 && Utils.TryParseUDouble(textBoxes[endStateColumnAux, j].Text, out endVolAux)) { double startState = Units.ConvertFrom(VolumeUnit, startVol + startVolAux); double endState = Units.ConvertFrom(VolumeUnit, endVol + endVolAux); double err = (refVolume != 0) ? 100.0 * (endState - startState - refVolume) / refVolume : 1000.0; largeExclamationLabel.Visible = exclamations[j].Visible = fixedErrorLimits ? ((err < warningLimLo) || (warningLimHi < err)) : ((err < -6) || (+6 < err)); } else if (startStateColumn >= 0 && Utils.TryParseUDouble(textBoxes[startStateColumn, j].Text, out startVol) && endStateColumn >= 0 && Utils.TryParseUDouble(textBoxes[endStateColumn, j].Text, out endVol)) { double startState = Units.ConvertFrom(VolumeUnit, startVol); double endState = Units.ConvertFrom(VolumeUnit, endVol); double err = (refVolume != 0) ? 100.0 * (endState - startState - refVolume) / refVolume : 1000.0; largeExclamationLabel.Visible = exclamations[j].Visible = fixedErrorLimits ? ((err < warningLimLo) || (warningLimHi < err)) : ((err < -6) || (+6 < err)); } else { largeExclamationLabel.Visible = exclamations[j].Visible = false; } } private void textBox_MouseDown(object sender, MouseEventArgs e) { textBox_AcceptsTabChanged(sender, e); } private void textBox_AcceptsTabChanged(object sender, EventArgs e) { if (!isHandlersEnabled || !isCameraPicture) return; string[] kj = (sender as TextBox).Name.Split(new char[] { '~' }); int k = int.Parse(kj[0]); int j = int.Parse(kj[1]); if (k == startStateColumn || k == endStateColumn) { LoadImage(sender, (k == startStateColumn) ? Tst.Start : Tst.End, j); } } /// /// Find the next enabled combo box, update k and i. /// Return true (=pastTheEndOfArray) when there is no such next combo box. /// /// 2-dimensional array of combo boxes /// 0-nased column /// 0-based row /// Order of progress to the next combo: true = left-right, false = top-down /// true (=pastTheEndOfArray) when there is no such next combo box, otherwise false bool FindNextEnabledTextBox(TextBox[,] textBoxes, ref int k, ref int i, bool isLrOrder, bool skipStartColumn = false) { bool pastTheEndOfArray = false; do { if (isLrOrder) { if (++k == textBoxes.GetLength(0)) { k = 0; if (++i == textBoxes.GetLength(1)) { i = 0; pastTheEndOfArray = true; break; } } } else { if (++i == textBoxes.GetLength(1)) { i = 0; if (++k == textBoxes.GetLength(0)) { k = 0; pastTheEndOfArray = true; break; } } } } while (!textBoxes[k, i].Enabled || (skipStartColumn && k == startStateColumn)); return pastTheEndOfArray; } /// /// Loads appropriate image into pictureBox1.Image /// Handles mouse clicks and Tab-changes inside text boxes. /// /// Tst.Start or Tst.End /// 0-based WM position void LoadImage(object sender, Tst startOrEnd, int rrIx) { string imageFileName; if (startOrEnd == Tst.Start && startImages[rrIx] != null) imageFileName = startImages[rrIx]; else if (startOrEnd == Tst.End && endImages[rrIx] != null) imageFileName = endImages[rrIx]; else imageFileName = string.Empty; if (imageFileName == lastImageName) { return; } lastImageName = imageFileName; lastWmNr1 = rrIx + 1; if (string.IsNullOrEmpty(imageFileName) || !File.Exists(imageFileName)) { lastImage = null; } else { using (Bitmap tempBmp = System.Drawing.Image.FromFile(imageFileName) as Bitmap) { Rectangle rect; if (tempBmp.Height < tempBmp.Width) { int hght = tempBmp.Height; int leftMargin = (tempBmp.Width - hght) / 2; rect = new Rectangle(leftMargin, 0, hght, hght); } else { int hght = tempBmp.Height; int wdth = tempBmp.Width; int margin = (hght - wdth) / 2; rect = new Rectangle(0, margin, wdth, wdth); } //int hght = tempBmp.Height; //int leftMargin = (tempBmp.Width - hght) / 2; //lastImage = new Bitmap(tempBmp.Clone(new Rectangle(leftMargin, 0, hght, hght), tempBmp.PixelFormat)); lastImage = new Bitmap(tempBmp.Clone(rect, tempBmp.PixelFormat)); if (isZoomed[wmNr0b]) { zoomedImage = MakeZoomedImage(lastImage, zoomCenterLoc[wmNr0b]); } } } ShowImage(); } /// /// Left click = Zoom, Right click = Unzoom /// private void pictureBox1_Click(object sender, EventArgs e) { if (lastImage == null) return; MouseEventArgs me = (MouseEventArgs)e; if (me.Button == MouseButtons.Left && !isZoomed[wmNr0b]) { switch (imageRotation) { case Rotation.R0: zoomCenterLoc[wmNr0b] = me.Location; break; case Rotation.R90: zoomCenterLoc[wmNr0b].X = pictureBox1.Size.Height - me.Location.Y - 1; zoomCenterLoc[wmNr0b].Y = me.Location.X; break; case Rotation.R180: zoomCenterLoc[wmNr0b].X = pictureBox1.Size.Width - me.Location.X - 1; zoomCenterLoc[wmNr0b].Y = pictureBox1.Size.Height - me.Location.Y - 1; break; case Rotation.R270: zoomCenterLoc[wmNr0b].X = me.Location.Y; zoomCenterLoc[wmNr0b].Y = pictureBox1.Size.Width - me.Location.X - 1; break; } zoomedImage = MakeZoomedImage(lastImage, zoomCenterLoc[wmNr0b]); isZoomed[wmNr0b] = true; ShowImage(); } else if (me.Button == MouseButtons.Right) { isZoomed[wmNr0b] = false; ShowImage(); } } private void rotateImageButton_Click(object sender, EventArgs e) { if (lastImage == null) return; if (++imageRotation == Rotation.Count) { imageRotation = Rotation.R0; } ShowImage(); } Bitmap MakeZoomedImage(Bitmap oriImage, Point centerLoc) { float picBoxW = (float)pictureBox1.Size.Width; float picBoxH = (float)pictureBox1.Size.Height; float newRelX = Math.Max(0, Math.Min(centerLoc.X / picBoxW - 0.25f, 0.5f)); float newRelY = Math.Max(0, Math.Min(centerLoc.Y / picBoxH - 0.25f, 0.5f)); Rectangle newRect = new Rectangle((int)(newRelX * oriImage.Width), (int)(newRelY * oriImage.Height), oriImage.Width / 2, oriImage.Height / 2); /// image resolution, 1280 x 960 return lastImage.Clone(newRect, oriImage.PixelFormat); } /// /// Used by ShowImage() /// Brush lightBrush = new SolidBrush(Color.LightGreen); Brush darkBrush = new SolidBrush(Color.Green); Font largeFont = new Font("arial", 24.0F, FontStyle.Bold); /// normal image Font smallFont = new Font("arial", 12.0F, FontStyle.Bold); /// zoomed image /// /// Show selected image in the picture box. /// lastWmNr1, lastImage, zoomedImage, isZoomed and imageRotation are used. /// void ShowImage() { if (pictureBox1 == null) return; if (lastImage == null) { pictureBox1.Image = new Bitmap(TBF.Properties.Resources.Empty); return; } Bitmap tempBmp = isZoomed[wmNr0b] ? new Bitmap(zoomedImage.Clone(new Rectangle(0, 0, zoomedImage.Width, zoomedImage.Height), zoomedImage.PixelFormat)) : new Bitmap(lastImage.Clone(new Rectangle(0, 0, lastImage.Width, lastImage.Height), lastImage.PixelFormat)); switch (imageRotation) { case Rotation.R90: tempBmp.RotateFlip(RotateFlipType.Rotate270FlipNone); break; case Rotation.R180: tempBmp.RotateFlip(RotateFlipType.Rotate180FlipNone); break; case Rotation.R270: tempBmp.RotateFlip(RotateFlipType.Rotate90FlipNone); break; } /// Write water meter number to the bitmap using (var grph = Graphics.FromImage(tempBmp)) { Font font = isZoomed[wmNr0b] ? smallFont : largeFont; int margin = isZoomed[wmNr0b] ? 1 : 2; int pixelsCount = 0; int brightness = 0; for (int x = margin; x <= 8 * margin; x += margin) { for (int y = margin; y <= 8 * margin; y += margin) { brightness += (int)tempBmp.GetPixel(x, y).G; pixelsCount++; } } brightness /= pixelsCount; Brush brush = (brightness > 160) ? darkBrush : lightBrush; grph.DrawString(lastWmNr1.ToString(), font, brush, margin, margin); } pictureBox1.Image = tempBmp; } #region Forced close handling public void StartForceCloseHandler() { UiBridge.Bridge.CloseModelessFormHandler += delegate(object sender, EventArgs args) { if (InvokeRequired) { Invoke(new EventHandler(OnForceClose), sender, args); } else OnForceClose(sender, args); }; } void OnForceClose(object sender, EventArgs args) { DialogResult = DialogResult.Cancel; Close(); } #endregion } }