From 800ec41254ee5970ea16b2486435fd5cb69c757a Mon Sep 17 00:00:00 2001 From: "Adriano (daitarn)" Date: Sat, 24 Oct 2026 17:03:16 +0200 Subject: [PATCH] =?UTF-8?q?acquisitore:=20=C2=ABAttiva=20calibrazione?= =?UTF-8?q?=C2=BB=20con=20scelta=20e=20CONFERMA=20sotto=20l'immagine=20cal?= =?UTF-8?q?ibrata=20(Refs=20#93)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 5.5 --- scripts/calibrazione_pagina.py | 76 ++++++++++++++++++----- scripts/tests/test_pagina_acquisizione.py | 2 +- scripts/tests/test_pagina_tab.py | 20 +++++- 3 files changed, 82 insertions(+), 16 deletions(-) diff --git a/scripts/calibrazione_pagina.py b/scripts/calibrazione_pagina.py index 4cdb13a..0593227 100644 --- a/scripts/calibrazione_pagina.py +++ b/scripts/calibrazione_pagina.py @@ -46,6 +46,8 @@ PAGINA = r""" select:focus, input:focus, button:focus-visible { outline: 2px solid var(--accento); outline-offset: 1px; } input[type=range] { width: 100%; accent-color: var(--accento); } input[type=checkbox] { accent-color: var(--accento); } + .scelta-cal { margin-top: 8px; align-items: center; } /* #93 */ + .scelta-cal select { flex: 1 1 200px; min-width: 0; } /* #93: nessuno scorrimento a 400 px */ .riga { display: flex; flex-wrap: wrap; gap: 10px; align-items: flex-end; } .nota { font-size: .82rem; color: var(--secondario); margin: 8px 0 0; } button { padding: 7px 13px; font-size: .95rem; border: 0; border-radius: 6px; background: var(--accento); color: #fff; @@ -156,7 +158,7 @@ PAGINA = r""" - +
@@ -179,6 +181,12 @@ PAGINA = r"""
+ +
+ + +
+
@@ -618,7 +626,7 @@ $("avvia").onclick = avviaAnteprima; $("ferma").onclick = fermaAnteprima; $("scatta").onclick = scatta; $("azzeraMax").onclick = function () { A.nitMax = 0; $("nitidezzaMax").textContent = "-"; $("barraNitidezza").style.width = "0"; }; -// #92: il mirino vale per entrambi i riquadri; la casella «Mostra immagine calibrata» apre il riquadro e raddrizza il frame gia' tenuto +// #92: il mirino vale per entrambi i riquadri; #93: la casella «Attiva calibrazione» apre il riquadro e raddrizza il frame gia' tenuto $("mirinoCasella").onchange = function () { $("mirino").hidden = $("mirinoCal").hidden = !$("mirinoCasella").checked; }; function mostraRiquadroCal() { var si = $("mostraCal").checked; @@ -995,6 +1003,7 @@ function disegnaElenco() { annullaConferma(); t.textContent = ""; C.celle = []; + disegnaSceltaCal(); // #93: la combo sotto la calibrata segue l'elenco, anche se vuoto if (!C.elenco.length) { var vuota = document.createElement("div"); vuota.className = "nota"; @@ -1035,7 +1044,7 @@ function disegnaElenco() { var bc = bottone("Carica", "", function () { caricaNome(c.nome); }); bc.disabled = !$("sorgente").value; az.appendChild(bc); - if (attiva) az.appendChild(bottone("Scollega", "secondario", scollega)); + if (attiva) az.appendChild(bottone("Scollega", "secondario", function () { scollega("msgElenco"); })); // #93: senza l'evento come parametro az.appendChild(bottone("Elimina", "secondario", function (e) { eliminaNome(c.nome, e.currentTarget); })); sc.appendChild(riga1); sc.appendChild(riga2); @@ -1050,41 +1059,80 @@ function segnaRisoluzioni() { (C.celle || []).forEach(function (c) { c.span.hidden = !(A.ris && c.w > 0 && c.h > 0 && (A.ris.w !== c.w || A.ris.h !== c.h)); }); + // #93: stesso avviso nelle voci della combo; si cambia solo il testo, cosi' scelta e menu aperto restano + var sel = $("sceltaCal"); + for (var i = 0; i < sel.options.length; i++) if (sel.options[i].value) sel.options[i].textContent = testoVoceCal(sel.options[i].dataset); +} + +// #93: testo di una voce della combo: «nome · W×H · px/mm», con «risoluzione diversa» se il frame noto non combacia +function testoVoceCal(d) { + var w = Number(d.w), h = Number(d.h); + var t = d.nome + " · " + (w && h ? fmt(w, 0) + "×" + fmt(h, 0) : "-") + " · " + fmt(Number(d.px), 3) + " px/mm"; + if (A.ris && w > 0 && h > 0 && (A.ris.w !== w || A.ris.h !== h)) t += " — risoluzione diversa"; + return t; +} +// #93: ridisegna la combo (ogni ricarica dell'elenco); preselezionata la calibrazione attiva, senza sorgente tutto spento +function disegnaSceltaCal() { + var sel = $("sceltaCal"); + sel.textContent = ""; + var nessuna = document.createElement("option"); + nessuna.value = ""; + nessuna.textContent = "— nessuna (non calibrata) —"; + sel.appendChild(nessuna); + (C.elenco || []).forEach(function (c) { + var o = document.createElement("option"); + o.value = c.nome; + o.dataset.nome = c.nome; o.dataset.w = c.image_width || 0; o.dataset.h = c.image_height || 0; o.dataset.px = c.px_per_mm || 0; + o.textContent = testoVoceCal(o.dataset); + sel.appendChild(o); + }); + sel.value = C.attiva !== null && C.attiva !== undefined ? C.attiva : ""; + if (sel.selectedIndex < 0) sel.selectedIndex = 0; + sel.disabled = $("confermaCal").disabled = !$("sorgente").value; } // «Carica»: la calibrazione diventa quella attiva della sorgente. Un errore (es. risoluzione diversa) non ferma l'anteprima. -async function caricaNome(nome) { +async function caricaNome(nome, idMsg) { + idMsg = idMsg || "msgElenco"; // #93: il messaggio puo' stare sotto la calibrata (msgSceltaCal) var sorgente = $("sorgente").value; - if (!sorgente) { dire("msgElenco", "Scegliere una sorgente nella tab Acquisizione.", "errore"); return; } - dire("msgElenco", "Caricamento di «" + nome + "»...", ""); + if (!sorgente) { dire(idMsg, "Scegliere una sorgente nella tab Acquisizione.", "errore"); return; } + dire(idMsg, "Caricamento di «" + nome + "»...", ""); try { var r = await fetch("/camera/calibrazione-attiva", { method: "PUT", headers: JSON_H, body: JSON.stringify({ source: sorgente, nome: nome }) }); if (sorgente !== $("sorgente").value) return; - if (!r.ok) { dire("msgElenco", await dettaglio(r), "errore"); return; } - dire("msgElenco", "Calibrazione «" + nome + "» caricata sulla sorgente.", "ok"); + if (!r.ok) { dire(idMsg, await dettaglio(r), "errore"); disegnaSceltaCal(); return; } // #93: la combo torna sull'attiva + dire(idMsg, "Calibrazione «" + nome + "» caricata sulla sorgente.", "ok"); A.bloccoCal = false; // #92 - await aggiornaStato(); // prima lo stato nuovo, poi il riquadro calibrata con la nuova calibrazione + await aggiornaStato(); // prima lo stato nuovo (rilegge anche l'elenco, #93: schede e combo allineate), poi il riquadro calibrata aggiornaCalibrata(); } catch (e) { - dire("msgElenco", "Servizio non raggiungibile.", "errore"); + dire(idMsg, "Servizio non raggiungibile.", "errore"); + disegnaSceltaCal(); // #93 } } // «Scollega»: la sorgente torna senza calibrazione, la libreria resta -async function scollega() { +async function scollega(idMsg) { + idMsg = idMsg || "msgElenco"; // #93 var sorgente = $("sorgente").value; if (!sorgente) return; try { var r = await fetch("/camera/calibration?source=" + encodeURIComponent(sorgente), { method: "DELETE" }); - dire("msgElenco", r.ok ? "Calibrazione scollegata dalla sorgente (resta fra le salvate)." : await dettaglio(r), r.ok ? "ok" : "errore"); + dire(idMsg, r.ok ? "Calibrazione scollegata dalla sorgente (resta fra le salvate)." : await dettaglio(r), r.ok ? "ok" : "errore"); } catch (e) { - dire("msgElenco", "Servizio non raggiungibile.", "errore"); + dire(idMsg, "Servizio non raggiungibile.", "errore"); } A.bloccoCal = false; // #92 - await aggiornaStato(); + await aggiornaStato(); // #93: rilegge anche l'elenco, quindi la combo torna sull'attiva anche dopo un errore aggiornaCalibrata(); // #92: senza calibrazione il riquadro dice che non c'e' } +// #93: CONFERMA sotto la calibrata: voce scelta -> PUT calibrazione-attiva, «nessuna» -> DELETE calibration +$("confermaCal").onclick = function () { + var nome = $("sceltaCal").value; + if (nome) caricaNome(nome, "msgSceltaCal"); else scollega("msgSceltaCal"); +}; + // «Elimina» di una riga: conferma in pagina con un secondo clic entro 4 s (niente finestre del browser) function annullaConferma() { clearTimeout(C.confermaTimer); diff --git a/scripts/tests/test_pagina_acquisizione.py b/scripts/tests/test_pagina_acquisizione.py index e7b9d78..1c68ae3 100644 --- a/scripts/tests/test_pagina_acquisizione.py +++ b/scripts/tests/test_pagina_acquisizione.py @@ -42,7 +42,7 @@ def test_id_principali(): def test_sezioni_e_testi_italiani(): for testo in ("Sorgente", "Anteprima", "Parametri camera", "Calibrazione", "Salva come predefiniti", "Carica immagine da file...", "Questa sorgente non ha parametri regolabili", - "Mostra immagine calibrata"): # #92 + "Attiva calibrazione"): # #92 #93 assert testo in PAGINA, testo assert "it-IT" in PAGINA # numeri con la virgola assert "Choose File" not in PAGINA diff --git a/scripts/tests/test_pagina_tab.py b/scripts/tests/test_pagina_tab.py index b74d70c..5a2c91d 100644 --- a/scripts/tests/test_pagina_tab.py +++ b/scripts/tests/test_pagina_tab.py @@ -30,7 +30,7 @@ def test_sorgente_in_cima_poi_riquadri(): assert i_acq < i_sorg < i_ant < i_riq < PAGINA.index('id="sezioneParametri"') < i_cal assert PAGINA.index('id="sezioneCalibrazione"') > i_cal # barra: casella calibrata e mirino; la vecchia casella «Immagine grezza» non c'e' piu' - assert 'id="mostraCal"' in PAGINA and "Mostra immagine calibrata" in PAGINA + assert 'id="mostraCal"' in PAGINA and "Attiva calibrazione" in PAGINA assert 'id="grezzo"' not in PAGINA and "Immagine grezza (senza correzione)" not in PAGINA # riquadri: «Immagine» sempre visibile, «Calibrata» nascosta finche' la casella e' spenta assert re.search(r'