Files
Titano/Titano/Program.cs
T
Alby96andClaude Opus 5 15735a7b11 I comandi entrano nella scheda e la scheda prende tutta la finestra
I pulsanti contestuali stavano sulla riga del marchio, fuori dal riquadro della
scheda a cui appartenevano: per trovare il comando che riguardava una tabella
bisognava risalire fino al bordo della finestra. Ora stanno dentro la sezione,
sopra il contenuto su cui agiscono, e ciascuno spiega a comparsa cosa fa. Il
marchio scende nella barra delle sezioni, che diventa alta quanto la finestra e
si riduce ai soli simboli; a destra non resta altro che la scheda, dall'alto al
basso. Lo spazio liberato in cima ospita l'avanzamento delle operazioni di massa
— una barra sola, perche' la scheda Esportazione ne aveva una seconda che diceva
la stessa cosa piu' in basso — che passa anche sull'icona nella barra delle
applicazioni tramite ITaskbarList3 dichiarata a mano.

Durante un'operazione di massa i controlli che la disturberebbero sono spenti: il
progetto viene letto da un altro thread a ogni fotogramma, e cambiare risoluzione
o ritaglio a meta' significa cambiare le regole a partita iniziata.

L'anteprima guadagna due letture. La maschera del ritaglio mostra il fotogramma
intero scurendo cio' che il formato d'uscita taglia via, perche' il rapporto
d'immagine si sceglie guardando cosa si perde; e lo schermo intero apre il
fotogramma in una finestra a parte alla risoluzione nativa, con i dati di scatto
in un pannello che si chiude, perche' decidere se una stella e' puntiforme si fa
al cento per cento.

La scheda Esportazione si apre su una preimpostazione. I parametri di codifica
non sono indipendenti — il bitrate abbondante a 1080p lascia gradini sul cielo a
4K — e sceglierli bene significa muoverli insieme; restano tutti modificabili, e
toccarne uno riporta la voce a «Personalizzata».

Sull'annullamento: non sono riuscito a riprodurre un crollo sull'albero di
partenza in dodici scenari automatici, ma ho chiuso quattro strade per cui
poteva passare. Cancel() veniva chiamato nudo dentro il gestore del clic, dove
un'eccezione di una registrazione avrebbe ucciso la finestra. FrameWindow non
tollerava ObjectDisposedException quando il file di parcheggio veniva chiuso
sotto le decodifiche in volo, non impediva l'avvio di nuove decodifiche dopo la
chiusura, e lasciava non osservate le eccezioni dei compiti falliti. Soprattutto,
la coda di un'esportazione annullata tocca i controlli dopo che la finestra puo'
essere gia' stata chiusa — ed e' proprio la cosa piu' naturale da fare dopo aver
annullato: quel percorso ora si ferma prima. Resta una rete: le eccezioni non
gestite finiscono in Documenti\Titano\errori.txt invece di portarsi via il lavoro.

I dati dell'applicazione si spostano da AppData a Documenti, perche' sono file
fatti per essere aperti; le preferenze nella vecchia collocazione vengono
migrate. Tre nuove verifiche coprono l'annullamento con il parcheggio su disco
attivo: 63 su 63 passano, provate anche sulle sequenze reali in K:\Time Lapse.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-07 17:09:40 +02:00

156 lines
6.5 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.Runtime.InteropServices;
using Titano.Diagnostics;
namespace Titano;
internal static class Program
{
private const int AttachParentProcess = -1;
[DllImport("kernel32.dll")]
private static extern bool AttachConsole(int processId);
[STAThread]
private static int Main(string[] args)
{
if (args.Length > 0 && args[0] is "--selftest" or "-t")
{
EnsureConsole();
string directory = args.Length > 1
? args[1]
: Path.Combine(Path.GetTempPath(), "Titano.SelfTest");
Directory.CreateDirectory(directory);
Console.WriteLine();
Console.Write(SelfTest.DescribeEnvironment());
return SelfTest.Run(directory, Console.Out);
}
if (args.Length > 1 && args[0] == "--make-icon")
{
EnsureConsole();
IconGenerator.Write(args[1]);
Console.WriteLine($"Icona generata in {args[1]}.");
return 0;
}
if (args.Length > 1 && args[0] == "--diagnose")
{
EnsureConsole();
Console.WriteLine();
// Le parole chiave possono comparire in qualunque posizione: gli argomenti
// posizionali erano ormai troppi perché ricordarne l'ordine avesse senso.
var modules = SequenceDiagnostics.AdvancedModules.None;
int analysisFrames = 0;
var positional = new List<string>();
foreach (string argument in args[1..])
{
if (argument.Equals("avanzato", StringComparison.OrdinalIgnoreCase))
modules |= SequenceDiagnostics.AdvancedModules.Analysis;
else if (argument.Equals("camera", StringComparison.OrdinalIgnoreCase))
modules |= SequenceDiagnostics.AdvancedModules.Camera;
else if (argument.Equals("mediana", StringComparison.OrdinalIgnoreCase))
modules |= SequenceDiagnostics.AdvancedModules.MedianStack;
else if (argument.Equals("scie", StringComparison.OrdinalIgnoreCase))
modules |= SequenceDiagnostics.AdvancedModules.StarTrails;
else if (argument.Equals("luminanza", StringComparison.OrdinalIgnoreCase))
modules |= SequenceDiagnostics.AdvancedModules.RegionsByLuminance;
else if (argument.StartsWith("analisi=", StringComparison.OrdinalIgnoreCase))
int.TryParse(argument[8..], out analysisFrames);
else
positional.Add(argument);
}
return SequenceDiagnostics.Run(positional[0], Console.Out,
renderPath: positional.Count > 1 ? positional[1] : null,
renderFrames: positional.Count > 2 && int.TryParse(positional[2], out int n) ? n : 24,
renderWidth: positional.Count > 3 && int.TryParse(positional[3], out int w) ? w : 0,
renderCodec: positional.Count > 4 && positional[4].Equals("hevc", StringComparison.OrdinalIgnoreCase)
? Video.VideoCodec.Hevc : Video.VideoCodec.H264,
advanced: modules,
analysisFrames: analysisFrames);
}
if (args.Length > 1 && args[0] == "--capture")
{
EnsureConsole();
ApplicationConfiguration.Initialize();
return Capture(args[1], args.Length > 2 ? args[2] : null,
args.Length > 3 && int.TryParse(args[3], out int tab) ? tab : 0,
args.Length > 4 && args[4].Equals("avanzato", StringComparison.OrdinalIgnoreCase));
}
ApplicationConfiguration.Initialize();
CrashGuard.Install();
Application.Run(new UI.MainForm());
return 0;
}
/// <summary>
/// Rende visibili i messaggi delle modalità a riga di comando.
///
/// L'eseguibile è compilato come applicazione a finestre e quindi nasce senza console:
/// agganciare quella del processo padre è l'unico modo di farsi leggere da un terminale.
/// Ma se lo standard output è già dirottato — una pipe, un file di registro, il task di
/// un editor — l'aggancio sostituirebbe quel dirottamento e i messaggi finirebbero nella
/// console invece che dove sono attesi. In quel caso non si aggancia niente: i gestori
/// ereditati vanno già bene.
/// </summary>
private static void EnsureConsole()
{
if (Console.IsOutputRedirected) return;
AttachConsole(AttachParentProcess);
}
/// <summary>
/// Apre l'interfaccia, opzionalmente vi carica una sequenza, e ne salva un'immagine.
/// Serve a verificare la resa dei controlli disegnati a mano senza intervento manuale.
/// </summary>
private static int Capture(string outputPath, string? sequenceDirectory, int settingsTab,
bool advanced = false)
{
using var form = new UI.MainForm();
form.Show();
Pump(200);
// I moduli avanzati vanno accesi prima di caricare: la maschera delle regioni e il
// percorso della stabilizzazione nascono durante l'analisi, non dopo.
if (advanced) form.EnableAdvancedModulesForCapture();
if (sequenceDirectory is not null && Directory.Exists(sequenceDirectory))
{
string[] files = [.. Directory.EnumerateFiles(sequenceDirectory)
.Where(Metadata.MetadataReader.IsSupported)];
var work = form.PrepareForCaptureAsync(files);
while (!work.IsCompleted) Pump(30);
Pump(2500); // attesa del rendering asincrono dell'anteprima
}
if (settingsTab >= 0)
{
form.SelectSettingsTab(settingsTab);
Pump(200);
}
using var bitmap = new Bitmap(form.Width, form.Height);
form.DrawToBitmap(bitmap, new Rectangle(0, 0, form.Width, form.Height));
bitmap.Save(outputPath, System.Drawing.Imaging.ImageFormat.Png);
Console.WriteLine($"Interfaccia catturata in {outputPath} ({bitmap.Width}×{bitmap.Height}).");
return 0;
}
private static void Pump(int milliseconds)
{
var deadline = Environment.TickCount64 + milliseconds;
do
{
Application.DoEvents();
Thread.Sleep(10);
}
while (Environment.TickCount64 < deadline);
}
}