source: trunk/Packages/Common/UJobProgressView.pas

Last change on this file was 215, checked in by chronos, 3 years ago
  • Modified: Build under Lazarus 2.2.0.
  • Modified: Updated Common package.
File size: 17.9 KB
Line 
1unit UJobProgressView;
2
3{$MODE Delphi}
4
5interface
6
7uses
8 SysUtils, Variants, Classes, Graphics, Controls, Forms, Syncobjs,
9 Dialogs, ComCtrls, StdCtrls, ExtCtrls, fgl, UThreading, Math,
10 DateUtils;
11
12const
13 EstimatedTimeShowTreshold = 4;
14 EstimatedTimeShowTresholdTotal = 1;
15 UpdateInterval = 100; // ms
16
17type
18
19 { TProgress }
20
21 TProgress = class
22 private
23 FLock: TCriticalSection;
24 FOnChange: TNotifyEvent;
25 FText: string;
26 FValue: Integer;
27 FMax: Integer;
28 procedure SetMax(const AValue: Integer);
29 procedure SetText(AValue: string);
30 procedure SetValue(const AValue: Integer);
31 public
32 procedure Increment;
33 procedure Reset;
34 constructor Create;
35 destructor Destroy; override;
36 property Value: Integer read FValue write SetValue;
37 property Max: Integer read FMax write SetMax;
38 property Text: string read FText write SetText;
39 property OnChange: TNotifyEvent read FOnChange write FOnChange;
40 end;
41
42 TFormJobProgressView = class;
43 TJobProgressView = class;
44 TJobThread = class;
45 TJob = class;
46
47 TJobProgressViewMethod = procedure(Job: TJob) of object;
48
49 { TJob }
50
51 TJob = class
52 private
53 FTerminate: Boolean;
54 procedure SetTerminate(const AValue: Boolean);
55 public
56 StartTime: TDateTime;
57 EndTime: TDateTime;
58 ProgressView: TJobProgressView;
59 Title: string;
60 Method: TJobProgressViewMethod;
61 NoThreaded: Boolean;
62 WaitFor: Boolean;
63 Progress: TProgress;
64 Thread: TJobThread;
65 ResultString: string;
66 Finished: Boolean;
67 procedure AddLogItem(Value: string);
68 constructor Create;
69 destructor Destroy; override;
70 property Terminate: Boolean read FTerminate write SetTerminate;
71 end;
72
73 TJobs = class(TFPGObjectList<TJob>)
74 end;
75
76 TJobThread = class(TListedThread)
77 procedure Execute; override;
78 public
79 ProgressView: TJobProgressView;
80 Job: TJob;
81 end;
82
83 { TFormJobProgressView }
84
85 TFormJobProgressView = class(TForm)
86 ImageList1: TImageList;
87 LabelText: TLabel;
88 Label2: TLabel;
89 LabelOperation: TLabel;
90 LabelEstimatedTimePart: TLabel;
91 LabelEstimatedTimeTotal: TLabel;
92 ListViewJobs: TListView;
93 MemoLog: TMemo;
94 PanelText: TPanel;
95 PanelProgressTotal: TPanel;
96 PanelOperationsTitle: TPanel;
97 PanelLog: TPanel;
98 PanelOperations: TPanel;
99 PanelProgress: TPanel;
100 ProgressBarPart: TProgressBar;
101 ProgressBarTotal: TProgressBar;
102 TimerUpdate: TTimer;
103 procedure FormHide(Sender: TObject);
104 procedure FormShow(Sender: TObject);
105 procedure ReloadJobList;
106 procedure FormClose(Sender: TObject; var CloseAction: TCloseAction);
107 procedure ListViewJobsData(Sender: TObject; Item: TListItem);
108 procedure TimerUpdateTimer(Sender: TObject);
109 procedure FormCreate(Sender: TObject);
110 procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
111 procedure UpdateHeight;
112 public
113 JobProgressView: TJobProgressView;
114 end;
115
116 { TJobProgressView }
117
118 TJobProgressView = class(TComponent)
119 private
120 FAutoClose: Boolean;
121 Finished: Boolean;
122 FOnJobFinish: TJobProgressViewMethod;
123 FOnOwnerDraw: TNotifyEvent;
124 FOwnerDraw: Boolean;
125 FShowDelay: Integer;
126 FTerminate: Boolean;
127 FormList: TList;
128 TotalStartTime: TDateTime;
129 Log: TStringList;
130 FForm: TFormJobProgressView;
131 procedure SetTerminate(const AValue: Boolean);
132 procedure UpdateProgress;
133 procedure JobProgressChange(Sender: TObject);
134 public
135 Jobs: TJobs;
136 CurrentJob: TJob;
137 CurrentJobIndex: Integer;
138 constructor Create(TheOwner: TComponent); override;
139 destructor Destroy; override;
140 procedure Clear;
141 function AddJob(Title: string; Method: TJobProgressViewMethod;
142 NoThreaded: Boolean = False; WaitFor: Boolean = False): TJob;
143 procedure Start;
144 procedure Stop;
145 procedure TermSleep(Delay: Integer);
146 property Form: TFormJobProgressView read FForm;
147 property Terminate: Boolean read FTerminate write SetTerminate;
148 published
149 property OwnerDraw: Boolean read FOwnerDraw write FOwnerDraw;
150 property ShowDelay: Integer read FShowDelay write FShowDelay;
151 property AutoClose: Boolean read FAutoClose write FAutoClose;
152 property OnJobFinish: TJobProgressViewMethod read FOnJobFinish
153 write FOnJobFinish;
154 property OnOwnerDraw: TNotifyEvent read FOnOwnerDraw
155 write FOnOwnerDraw;
156 end;
157
158 //var
159 // FormJobProgressView: TFormJobProgressView;
160
161procedure Register;
162
163resourcestring
164 SExecuted = 'Executed';
165
166implementation
167
168{$R *.lfm}
169
170resourcestring
171 SPleaseWait = 'Please wait...';
172 STerminate = 'Termination';
173 SEstimatedTime = 'Estimated time: %s';
174 STotalEstimatedTime = 'Total estimated time: %s';
175 SFinished = 'Finished';
176
177procedure Register;
178begin
179 RegisterComponents('Common', [TJobProgressView]);
180end;
181
182{ TJobThread }
183
184procedure TJobThread.Execute;
185begin
186 try
187 try
188 //raise Exception.Create('Exception in job');
189 ProgressView.CurrentJob.Method(Job);
190 except
191 on E: Exception do begin
192 ProgressView.Terminate := True;
193 raise;
194 end;
195 end;
196 finally
197 Terminate;
198 end;
199end;
200
201{ TFormJobProgressView }
202
203procedure TFormJobProgressView.UpdateHeight;
204var
205 H: Integer;
206 PanelOperationsVisible: Boolean;
207 PanelOperationsHeight: Integer;
208 PanelProgressVisible: Boolean;
209 PanelProgressTotalVisible: Boolean;
210 PanelLogVisible: Boolean;
211 MemoLogHeight: Integer = 200;
212 I: Integer;
213 ItemRect: TRect;
214 MaxH: Integer;
215begin
216 H := PanelOperationsTitle.Height;
217 PanelOperationsVisible := JobProgressView.Jobs.Count > 0;
218 if PanelOperationsVisible <> PanelOperations.Visible then
219 PanelOperations.Visible := PanelOperationsVisible;
220 if ListViewJobs.Items.Count > 0 then begin
221 Maxh := 0;
222 for I := 0 to ListViewJobs.Items.Count - 1 do
223 begin
224 ItemRect := ListViewJobs.Items[i].DisplayRect(drBounds);
225 Maxh := Max(Maxh, ItemRect.Top + (ItemRect.Bottom - ItemRect.Top));
226 end;
227 PanelOperationsHeight := Scale96ToScreen(12) + Maxh;
228 end else PanelOperationsHeight := Scale96ToScreen(8);
229 if PanelOperationsHeight <> PanelOperations.Height then
230 PanelOperations.Height := PanelOperationsHeight;
231 if PanelOperationsVisible then
232 H := H + PanelOperations.Height;
233
234 PanelProgressVisible := (JobProgressView.Jobs.Count > 0) and not JobProgressView.Finished;
235 if PanelProgressVisible <> PanelProgress.Visible then
236 PanelProgress.Visible := PanelProgressVisible;
237 if PanelProgressVisible then
238 H := H + PanelProgress.Height;
239 PanelProgressTotalVisible := (JobProgressView.Jobs.Count > 1) and not JobProgressView.Finished;
240 if PanelProgressTotalVisible <> PanelProgressTotal.Visible then
241 PanelProgressTotal.Visible := PanelProgressTotalVisible;
242 if PanelProgressTotalVisible then
243 H := H + PanelProgressTotal.Height;
244 Constraints.MinHeight := H;
245 PanelLogVisible := MemoLog.Lines.Count > 0;
246 if PanelLogVisible <> PanelLog.Visible then
247 PanelLog.Visible := PanelLogVisible;
248 if PanelLogVisible then
249 H := H + Scale96ToScreen(MemoLogHeight);
250 if PanelText.Visible then
251 H := H + PanelText.Height;
252 if Height <> H then begin
253 Height := H;
254 Top := (Screen.Height - H) div 2;
255 end;
256end;
257
258procedure TFormJobProgressView.TimerUpdateTimer(Sender: TObject);
259var
260 ProgressBarPartVisible: Boolean;
261 ProgressBarTotalVisible: Boolean;
262begin
263 JobProgressView.UpdateProgress;
264 if Visible and (not ProgressBarPart.Visible) and
265 Assigned(JobProgressView.CurrentJob) and
266 (JobProgressView.CurrentJob.Progress.Value > 0) then begin
267 ProgressBarPartVisible := True;
268 if ProgressBarPartVisible <> ProgressBarPart.Visible then
269 ProgressBarPart.Visible := ProgressBarPartVisible;
270 ProgressBarTotalVisible := True;
271 if ProgressBarTotalVisible <> ProgressBarTotal.Visible then
272 ProgressBarTotal.Visible := ProgressBarTotalVisible;
273 end;
274 if not Visible then begin
275 TimerUpdate.Interval := UpdateInterval;
276 if not JobProgressView.OwnerDraw then Show;
277 end;
278 if Assigned(JobProgressView.CurrentJob) then begin
279 LabelText.Caption := JobProgressView.CurrentJob.Progress.Text;
280 if LabelText.Caption <> '' then begin
281 PanelText.Visible := True;
282 UpdateHeight;
283 end;
284 end;
285end;
286
287procedure TFormJobProgressView.ListViewJobsData(Sender: TObject; Item: TListItem);
288begin
289 if (Item.Index >= 0) and (Item.Index < JobProgressView.Jobs.Count) then
290 with JobProgressView.Jobs[Item.Index] do begin
291 Item.Caption := Title;
292 if Item.Index = JobProgressView.CurrentJobIndex then Item.ImageIndex := 1
293 else if Finished then Item.ImageIndex := 0
294 else Item.ImageIndex := 2;
295 Item.Data := JobProgressView.Jobs[Item.Index];
296 end;
297end;
298
299procedure TFormJobProgressView.FormClose(Sender: TObject;
300 var CloseAction: TCloseAction);
301begin
302end;
303
304procedure TFormJobProgressView.FormCreate(Sender: TObject);
305begin
306 Caption := SPleaseWait;
307 try
308 //Animate1.FileName := ExtractFileDir(UTF8Encode(Application.ExeName)) +
309 // DirectorySeparator + 'horse.avi';
310 //Animate1.Active := True;
311 except
312
313 end;
314end;
315
316procedure TFormJobProgressView.ReloadJobList;
317begin
318 // Workaround for not showing first line
319 //Form.ListViewJobs.Items.Count := Jobs.Count + 1;
320 //Form.ListViewJobs.Refresh;
321
322 if ListViewJobs.Items.Count <> JobProgressView.Jobs.Count then
323 ListViewJobs.Items.Count := JobProgressView.Jobs.Count;
324 ListViewJobs.Refresh;
325 Application.ProcessMessages;
326 UpdateHeight;
327end;
328
329procedure TFormJobProgressView.FormShow(Sender: TObject);
330begin
331 ReloadJobList;
332end;
333
334procedure TFormJobProgressView.FormHide(Sender: TObject);
335begin
336 JobProgressView.Jobs.Clear;
337 ReloadJobList;
338end;
339
340procedure TFormJobProgressView.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
341begin
342 CanClose := JobProgressView.Finished;
343 JobProgressView.Terminate := True;
344 Caption := SPleaseWait + STerminate;
345end;
346
347
348{ TJobProgressView }
349
350function TJobProgressView.AddJob(Title: string; Method: TJobProgressViewMethod;
351 NoThreaded: Boolean = False; WaitFor: Boolean = False): TJob;
352begin
353 Result := TJob.Create;
354 Result.ProgressView := Self;
355 Result.Title := Title;
356 Result.Method := Method;
357 Result.NoThreaded := NoThreaded;
358 Result.WaitFor := WaitFor;
359 Result.Progress.Max := 100;
360 Result.Progress.Reset;
361 Result.Progress.OnChange := JobProgressChange;
362 Jobs.Add(Result);
363 //ReloadJobList;
364end;
365
366procedure TJobProgressView.Start;
367var
368 I: Integer;
369begin
370 Terminate := False;
371
372 if not OwnerDraw then Form.BringToFront;
373
374 Finished := False;
375 Form.Caption := SPleaseWait;
376 try
377 FormList := Screen.DisableForms(Form);
378 Log.Clear;
379 Form.MemoLog.Clear;
380
381 Form.PanelText.Visible := False;
382 Form.LabelEstimatedTimePart.Visible := False;
383 Form.LabelEstimatedTimeTotal.Visible := False;
384
385 CurrentJob := nil;
386 if ShowDelay = 0 then begin
387 Form.TimerUpdate.Interval := UpdateInterval;
388 Form.TimerUpdate.Enabled := True;
389 Form.TimerUpdateTimer(Self);
390 end else begin
391 Form.TimerUpdate.Interval := ShowDelay;
392 Form.TimerUpdate.Enabled := True;
393 end;
394
395 TotalStartTime := Now;
396 Form.ProgressBarTotal.Position := 0;
397 Form.ProgressBarTotal.Visible := False;
398 //UpdateHeight;
399
400 I := 0;
401 while I < Jobs.Count do
402 with Jobs[I] do begin
403 CurrentJobIndex := I;
404 CurrentJob := Jobs[I];
405 JobProgressChange(Self);
406 StartTime := Now;
407 Form.LabelEstimatedTimePart.Caption := Format(SEstimatedTime, ['']);
408 Form.ProgressBarPart.Position := 0;
409 Form.ProgressBarPart.Visible := False;
410 //Show;
411 Form.ReloadJobList;
412 Application.ProcessMessages;
413 if NoThreaded then begin
414 Thread := nil;
415 Method(CurrentJob);
416 end else begin
417 Thread := TJobThread.Create(True);
418 try
419 with Thread do begin
420 FreeOnTerminate := False;
421 Job := CurrentJob;
422 Name := 'Job: ' + Job.Title;
423 ProgressView := Self;
424 Start;
425 while not Terminated do begin
426 Application.ProcessMessages;
427 Sleep(1);
428 end;
429 WaitFor;
430 end;
431 finally
432 FreeAndNil(Thread);
433 end;
434 end;
435 Form.ProgressBarPart.Hide;
436 if Assigned(FOnJobFinish) then
437 FOnJobFinish(CurrentJob);
438 if Terminate then Break;
439 EndTime := Now;
440 Finished := True;
441 Inc(I);
442 end;
443 finally
444 CurrentJob := nil;
445 Form.TimerUpdate.Enabled := False;
446 Screen.EnableForms(FormList);
447 //if Visible then Hide;
448 Form.MemoLog.Lines.Assign(Log);
449 if (Form.MemoLog.Lines.Count = 0) and FAutoClose then begin
450 Form.Hide;
451 end;
452 if not Form.Visible then Clear;
453 Form.Caption := SFinished;
454 //LabelEstimatedTimePart.Visible := False;
455 Finished := True;
456 CurrentJobIndex := -1;
457 Form.ReloadJobList;
458 end;
459end;
460
461procedure TJobProgressView.JobProgressChange(Sender: TObject);
462begin
463 if Assigned(FOnOwnerDraw) then
464 FOnOwnerDraw(Self);
465end;
466
467procedure TJobProgressView.Stop;
468begin
469 Terminate := True;
470end;
471
472procedure TJobProgressView.TermSleep(Delay: Integer);
473const
474 Quantum = 100;
475var
476 I: Integer;
477begin
478 Sleep(Delay mod Quantum);
479 for I := 1 to (Delay div Quantum) do begin
480 if Terminate then Break;
481 Sleep(Quantum);
482 end;
483end;
484
485procedure TJobProgressView.SetTerminate(const AValue: Boolean);
486var
487 I: Integer;
488begin
489 if AValue = FTerminate then Exit;
490 for I := 0 to Jobs.Count - 1 do
491 Jobs[I].Terminate := AValue;
492 FTerminate := AValue;
493end;
494
495procedure TJobProgressView.UpdateProgress;
496const
497 OneJobValue: Integer = 100;
498var
499 TotalMax: Integer;
500 TotalValue: Integer;
501 EstimatedTimePart: TDateTime;
502 RemainingTime: TDateTime;
503begin
504 if Assigned(CurrentJob) then
505 with CurrentJob, Form do begin
506 // Part progress
507 ProgressBarPart.Max := Progress.Max;
508 ProgressBarPart.Position := Progress.Value;
509 if (Progress.Value >= EstimatedTimeShowTreshold) then begin
510 EstimatedTimePart := (Now - StartTime) / Progress.Value * (Progress.Max - Progress.Value);
511 LabelEstimatedTimePart.Caption := Format(SEstimatedTime, [
512 TimeToStr(EstimatedTimePart)]);
513 LabelEstimatedTimePart.Visible := True;
514 end;
515
516 // Total progress
517 TotalMax := Jobs.Count * OneJobValue;
518 TotalValue := Int64(CurrentJobIndex) * OneJobValue +
519 Round(Progress.Value / Progress.Max * OneJobValue);
520 ProgressBarTotal.Max := TotalMax;
521 ProgressBarTotal.Position := TotalValue;
522 if (TotalValue >= EstimatedTimeShowTresholdTotal) then begin
523 // Project estimated time according part estimated time plus
524 // estimated time by elapsed time divided by elapsed ticks mutiplied by rest ticks
525 RemainingTime := EstimatedTimePart +
526 (Now - TotalStartTime + EstimatedTimePart) /
527 ((CurrentJobIndex + 1) * OneJobValue) *
528 ((Jobs.Count - 1 - CurrentJobIndex) * OneJobValue);
529 if (RemainingTime > 0) and (RemainingTime < EncodeDate(2100, 1, 1)) then begin
530 LabelEstimatedTimeTotal.Caption := Format(STotalEstimatedTime, [
531 TimeToStr(RemainingTime)]);
532 LabelEstimatedTimeTotal.Visible := True;
533 end else begin
534 LabelEstimatedTimeTotal.Visible := False;
535 end;
536 end;
537 end;
538end;
539
540constructor TJobProgressView.Create(TheOwner: TComponent);
541begin
542 inherited;
543 if not (csDesigning in ComponentState) then begin
544 FForm := TFormJobProgressView.Create(Self);
545 FForm.JobProgressView := Self;
546 end;
547 Jobs := TJobs.Create;
548 Log := TStringList.Create;
549 //PanelOperationsTitle.Height := 80;
550 AutoClose := True;
551 ShowDelay := 0;
552end;
553
554procedure TJobProgressView.Clear;
555begin
556 Jobs.Clear;
557 Log.Clear;
558 //ReloadJobList;
559end;
560
561destructor TJobProgressView.Destroy;
562begin
563 FreeAndNil(Log);
564 FreeAndNil(Jobs);
565 inherited;
566end;
567
568{ TProgress }
569
570procedure TProgress.SetMax(const AValue: Integer);
571begin
572 try
573 FLock.Acquire;
574 FMax := AValue;
575 if FMax < 1 then FMax := 1;
576 if FValue >= FMax then FValue := FMax;
577 finally
578 FLock.Release;
579 end;
580end;
581
582procedure TProgress.SetText(AValue: string);
583begin
584 try
585 FLock.Acquire;
586 if FText = AValue then Exit;
587 FText := AValue;
588 finally
589 FLock.Release;
590 end;
591end;
592
593procedure TProgress.SetValue(const AValue: Integer);
594var
595 Change: Boolean;
596begin
597 try
598 FLock.Acquire;
599 if AValue < Max then begin
600 Change := AValue <> FValue;
601 FValue := AValue;
602 if Change and Assigned(FOnChange) then
603 try
604 FLock.Release;
605 FOnChange(Self);
606 finally
607 FLock.Acquire;
608 end;
609 end;
610 finally
611 FLock.Release;
612 end;
613end;
614
615procedure TProgress.Increment;
616begin
617 FLock.Acquire;
618 try
619 Value := Value + 1;
620 finally
621 FLock.Release;
622 end;
623end;
624
625procedure TProgress.Reset;
626begin
627 FLock.Acquire;
628 try
629 FValue := 0;
630 finally
631 FLock.Release;
632 end;
633end;
634
635constructor TProgress.Create;
636begin
637 FMax := 100;
638 FLock := TCriticalSection.Create;
639end;
640
641destructor TProgress.Destroy;
642begin
643 FLock.Free;
644 inherited Destroy;
645end;
646
647{ TJob }
648
649procedure TJob.SetTerminate(const AValue: Boolean);
650begin
651 if FTerminate = AValue then Exit;
652 FTerminate := AValue;
653 if AValue then begin
654 ProgressView.Terminate := AValue;
655 if Assigned(Thread) then Thread.Terminate;
656 end;
657end;
658
659procedure TJob.AddLogItem(Value: string);
660begin
661 with ProgressView do begin
662 Log.Add(Value);
663 end;
664end;
665
666constructor TJob.Create;
667begin
668 Progress := TProgress.Create;
669 Terminate := False;
670 Finished := False;
671end;
672
673destructor TJob.Destroy;
674begin
675 FreeAndNil(Progress);
676 inherited;
677end;
678
679end.
Note: See TracBrowser for help on using the repository browser.