nlve/src/uProgressForm.pas
4aiman 44c0a5de24 Показатель работы: число и полоса показывают одно и то же
Полоса ползла по догадке, а подпись показывала замер — и человек видел
полосу, заполненную на девять десятых, рядом с нулём процентов. Что в
этот момент происходит, по такой паре понять нельзя.

Теперь доля одна на обоих. Берётся она у самого ffmpeg: сборка участка с
затуханием идёт минуту и дольше, а «кусок 1 из 1» всё это время не
меняется, поэтому ffmpeg спрашивают ключом -progress, докуда он дошёл.
Пока он не ответил ни разу, полоса трогается с места на пару процентов и
там стоит — пустая полоса и есть правда о только что начатой работе.

Догадка заодно перестала закрывать промежуток до вехи целиком: предел —
девять десятых, остаток виден глазом. Проверка на это добавлена в
tests/progress.

Полоса в окне сборки была системной, и под gtk2 заполнялась ступенями по
восемь кусков на ширину. Теперь там та же своя полоса, что и внизу
главного окна, — заполняется сплошь. Чтобы она читалась из файла формы,
у неё опубликованы Anchors, Align и Visible: читатель формы берёт только
опубликованные свойства, и без этого окно падало при открытии.

Проверено глазами на невидимом экране: «Собираю наложение 1 из 1 — 2%»
при полосе в 2%, через четыре секунды 69% при полосе в 69%.
2026-10-01 15:50:10 +03:00

186 lines
6.8 KiB
ObjectPascal
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.

{
uProgressForm — окно хода долгой операции.
Показывается модально, пока работает рабочий поток. Поток шлёт сюда
состояние через Synchronize, поэтому все методы этого окна вызываются
из потока интерфейса и никакой защиты не требуют.
Кнопка одна и меняет назначение: пока идёт работа — «Отмена», после
завершения — «Закрыть». Так окно нельзя закрыть, не остановив работу,
и не приходится гадать, что стало с процессом.
}
unit uProgressForm;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, Forms, Controls, Graphics, ComCtrls, StdCtrls, ExtCtrls,
uTheme, uLogView;
type
{ TProgressForm }
TProgressForm = class(TForm)
btnAction: TThemedButton;
lblStage: TLabel;
lblEta: TLabel;
memoLog: TLogView;
{ Полоса своя, а не системная: TProgressBar под gtk2 рисуется
ступеньками, по восемь кусков на всю ширину, и ход работы в ней
виден рывками. Эта заполняется сплошь, как та, что внизу главного
окна. }
pbBar: TThemedProgress;
pnlTop: TPanel;
procedure btnActionClick(Sender: TObject);
procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
procedure FormCreate(Sender: TObject);
private
FFinished: Boolean;
FCancelRequested: Boolean;
FOnCancelRequest: TNotifyEvent;
{ Когда началась работа и какую долю показывали в прошлый раз: по ним
считается, сколько осталось. }
FStartedAt: TDateTime;
FLastFraction: Double;
function FormatSpan(const ASeconds: Double): string;
public
procedure SetStage(const AText: string);
procedure SetFraction(AValue: Double);
procedure AddLog(const AText: string);
{ Переводит окно в завершённое состояние: прогресс на месте, кнопка
становится «Закрыть». }
procedure Finish(ASuccess: Boolean; const AMessage: string);
property Finished: Boolean read FFinished;
property OnCancelRequest: TNotifyEvent read FOnCancelRequest
write FOnCancelRequest;
end;
implementation
{$R *.lfm}
{ TProgressForm }
procedure TProgressForm.FormCreate(Sender: TObject);
begin
{ Цвета задаём сами: системная тема может быть светлой, и тогда
тёмный таймлайн оказался бы в белой рамке. }
ApplyDarkTheme(Self);
pbBar.Fraction := 0;
memoLog.Clear;
btnAction.Caption := 'Отмена';
FStartedAt := Now;
FLastFraction := 0;
lblEta.Caption := '';
end;
procedure TProgressForm.SetStage(const AText: string);
begin
lblStage.Caption := AText;
end;
{ Время в виде «3 мин 20 с»: секунды сотнями человек не читает. }
function TProgressForm.FormatSpan(const ASeconds: Double): string;
var
Total, M, S: Integer;
begin
Total := Round(ASeconds);
if Total < 0 then
Total := 0;
M := Total div 60;
S := Total mod 60;
if M >= 60 then
Result := Format('%d ч %d мин', [M div 60, M mod 60])
else if M > 0 then
Result := Format('%d мин %d с', [M, S])
else
Result := Format('%d с', [S]);
end;
{ Сколько прошло и сколько осталось.
Осталось считается по простому правилу: если четверть пути заняла
минуту, весь путь займёт четыре. На деле скорость меняется — куски,
которые копируются, идут мгновенно, а те, что пересобираются,
медленно, — поэтому число называется «примерно» и пересчитывается на
каждом шаге. Даже такая оценка отвечает на главный вопрос: успею ли я
сходить за чаем. }
procedure TProgressForm.SetFraction(AValue: Double);
var
{ Не Left: так называется свойство формы. }
Elapsed, Remain: Double;
begin
if AValue < 0 then
AValue := 0;
if AValue > 1 then
AValue := 1;
pbBar.Fraction := AValue;
FLastFraction := AValue;
Elapsed := (Now - FStartedAt) * SecsPerDay;
{ Пока сделано меньше сотой доли, оценка была бы гаданием: первые
секунды уходят на запуск ffmpeg, а не на работу. }
if (AValue > 0.01) and (Elapsed > 1) then
begin
Remain := Elapsed * (1 - AValue) / AValue;
lblEta.Caption := Format('Прошло %s, осталось примерно %s (%d%%)',
[FormatSpan(Elapsed), FormatSpan(Remain), Round(AValue * 100)]);
end
else
lblEta.Caption := 'Прошло ' + FormatSpan(Elapsed);
end;
procedure TProgressForm.AddLog(const AText: string);
begin
{ Свой журнал сам держится конца, пока пользователь не отмотал назад, и
сам обрезает старые строки. }
memoLog.Add(AText);
end;
procedure TProgressForm.Finish(ASuccess: Boolean; const AMessage: string);
begin
FFinished := True;
btnAction.Caption := 'Закрыть';
lblStage.Caption := AMessage;
{ На месте оценки — итог: сколько всё это заняло. }
lblEta.Caption := 'Заняло ' + FormatSpan((Now - FStartedAt) * SecsPerDay);
if ASuccess then
lblStage.Font.Color := clDefault
else
lblStage.Font.Color := clRed;
end;
procedure TProgressForm.btnActionClick(Sender: TObject);
begin
if FFinished then
begin
Close;
Exit;
end;
{ Повторное нажатие ничего не даёт: остановка не мгновенна, надо
дождаться, пока ffmpeg завершится. }
if FCancelRequested then
Exit;
FCancelRequested := True;
btnAction.Enabled := False;
lblStage.Caption := 'Останавливаю...';
if Assigned(FOnCancelRequest) then
FOnCancelRequest(Self);
end;
procedure TProgressForm.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
{ Пока работа идёт, закрытие окна означало бы оставить ffmpeg работать
без всякого присмотра. Вместо этого запрашиваем остановку. }
CanClose := FFinished;
if not CanClose then
btnActionClick(nil);
end;
end.