source: trunk/Packages/Common/UJobProgressView.pas

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