/usr/src/castle-game-engine-6.4/base/castleprogress.pas is in castle-game-engine-src 6.4+dfsg1-2.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 | {
Copyright 2002-2017 Michalis Kamburelis.
This file is part of "Castle Game Engine".
"Castle Game Engine" is free software; see the file COPYING.txt,
included in this distribution, for details about the copyright.
"Castle Game Engine" is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
----------------------------------------------------------------------------
}
{ Progress bar functionality (TProgress, global variable Progress). }
unit CastleProgress;
{$I castleconf.inc}
{ Define this only for testing }
{ $define TESTING_PROGRESS_DELAY}
interface
uses SysUtils, CastleUtils, CastleTimeUtils;
type
TProgress = class;
{ Abstract user interface of the progress bar.
See @link(TProgress) for information how to use progress bars. }
TProgressUserInterface = class
private
FBarYPosition: Single;
FImage: TObject;
FOwnsImage: boolean;
procedure SetImage(const Value: TObject);
public
const
DefaultBarYPosition = 0.5;
constructor Create;
destructor Destroy; override;
{ Image displayed as a background of the progress bar.
Not all progress bar interfaces support it, some simply ignore it.
You can leave it @nil, then the interface will use whatever is suitable
(e.g. capture screen contents each time the progress bar starts).
Whether the image assigned here is "owned" (that is, "automatically
freed") by TProgressUserInterface instance depends on OwnsImage.
In any case, we don't modify the image
(if we need to resize it to fit the screen size,
we do it on a temporary copy).
The type of this must be @link(CastleImages.TRGBImage), but it cannot
be declared as such here, we want this unit to be part of base
units, not dependent on images. }
property Image: TObject read FImage write SetImage;
property OwnsImage: boolean read FOwnsImage write FOwnsImage default false;
{ Vertical position of the displayed progress bar.
This feature is supposed to indicate a suitable free space on the
background @link(Image) where we can nicely fit the progress bar UI.
Not all progress bar interfaces support it, some simply ignore it.
0 means the middle of progress bar is at the bottom of the image,
1 means at the top. 0.5 indicates the middle, and it's the default. }
property BarYPosition: Single read FBarYPosition write FBarYPosition
{$ifdef FPC} default DefaultBarYPosition {$endif};
{ Deprecated name for BarYPosition. }
property ImageBarYPosition: Single read FBarYPosition write FBarYPosition
{$ifdef FPC} default DefaultBarYPosition; deprecated {$endif};
{ Show progress bar. }
procedure Init(Progress: TProgress); virtual; abstract;
{ Update progress bar (because Progress.Position changed). }
procedure Update(Progress: TProgress); virtual; abstract;
{ Hide progress bar. }
procedure Fini(Progress: TProgress); virtual; abstract;
end;
{ Progress bar functionality.
This provides the functionality of a progress bar (everything that
wants to signal progress should call @link(Progress) methods),
but not the actual user interface. The user interface is "pluggable",
that is you assign something to the Progress.UserInterface property.
See the units:
@unorderedList(
@itemSpacing Compact
@item(CastleWindowProgress --- show progress bar in OpenGL window)
@item(CastleProgressConsole --- show progress bar on StdErr)
@item(And you can also implement progress handling yourself,
e.g. using Lazarus form or using Lazarus progress bar on existing form.)
)
This way any unit that implements some lengthy operation can call
appropriate functions of the @link(Progress) object. And the final program
can choose how it wants to show that progress to user (in console?
in OpenGL window? etc.).
Usage example:
@longcode(#
Progress.UserInterface := ... some TProgressUserInterface instance ...;
...
Progress.Init(100, 'Doing something time-consuming, please wait');
try
for i := 1 to 100 do
begin
... do something ...
Progress.Step;
end;
finally Progress.Fini; end;
#)
Using @code("try ... finally ... end") above is not strictly required,
but is strongly suggested. Rule of thumb says to always call
Progress.Fini when you called Progress.Init.
The @link(TProgress.Step) is implemented such that you don't have to
worry about calling it too often. We will not update the interface
(@link(TProgressUserInterface.Update)) too often,
see TProgress.UpdatePart and TProgress.UpdateDelay for details.
This unit creates one instance of the class @link(TProgress): @link(Progress).
Usually this is what you want to use. For complicated cases,
you can create and pass around more instances
(e.g. from different threads, each @link(TProgress) object displaying
it's state in a separate window.) }
TProgress = class
private
FUserInterface: TProgressUserInterface;
FUpdatePart: Cardinal;
FUpdateDelay: TFloatTime;
FMax, FPosition: Cardinal;
{ Variables below are meaningfull only if Active.
When UserInterfaceDelayed, this is the time and position (always 0)
of the TProgress.Init call.
When not UserInterfaceDelayed, this is the time and position
of the last TProgress.Init or TProgress.Update call. }
LastUpdatePos: Cardinal;
LastUpdateTime: TTimerResult;
UserInterfaceDelayed: boolean;
FTitle: string;
FActive: boolean;
procedure SetPosition(const Value: Cardinal);
public
const
{ }
DefaultUpdatePart = {$ifdef TESTING_PROGRESS_DELAY} 100000000 {$else} 100 {$endif};
DefaultUpdateDelay = {$ifdef TESTING_PROGRESS_DELAY} 0 {$else} 0.25 {$endif};
property UserInterface: TProgressUserInterface
read FUserInterface write FUserInterface;
{ Define how often to redraw interface (TProgressUserInterface.Update).
Position must change by (1/UpdatePart) * Max and at the same time
at least UpdateDelay seconds must pass to redraw.
This allows you to call @link(Step) very often, without worrying
that you cause redraw too often (which would cause slowdown).
UpdateDelay default value is DefaultUpdateDelay.
@groupBegin }
property UpdatePart: Cardinal read FUpdatePart write FUpdatePart
default DefaultUpdatePart;
property UpdateDelay: TFloatTime read FUpdateDelay write FUpdateDelay;
{ @groupEnd }
{ Current Position of the progress bar.
Always >= 0 and <= @link(Max).
You can set this property only when @link(Active).
Setting it to something > @link(Max) will be silently clamped to @link(Max).
You can only increase it (trying to decrease it will be silently
ignored, which is useful if your position information is only an
approximation).
In other words, setthing this property is equivalent
to appropriate @link(Step) call. }
property Position: Cardinal read FPosition write SetPosition;
property Max: Cardinal read FMax;
property Title: string read FTitle;
{ Are we between Init and Fini calls.
Init changes Active to true, Fini changes Active to false. }
property Active: boolean read FActive;
{ Start the progress bar.
You can call Init only when Active = false (that is, you
cannot Init while another progress is working).
Initializes @link(Max), @link(Title), sets @link(Position) to 0 and
changes @link(Active) to true.
UserInterface must be initialized (non-nil) when calling
Init, and you cannot change UserInterface when progress is Active
(i.e. before you call Fini).
If DelayUserInterface is set to @true, a very useful optimization
is performed: TProgress.Init will not
immediately result in TProgressUserInterface.Init call.
Instead, actual initialization of the interface will be delayed
until some TProgress.Update, when UpdateDelay time will pass.
The advantage of DelayUserInterface is that if
an operation will take a very short time, we will not waste
time on possibly lengthy initialization of the progress bar
interface. For example, CastleWindowProgress may have to capture OpenGL screen
at the initialization, which takes a noticeable fraction of second
by itself. So it's not sensible to init CastleWindowProgress if an entire
operation between Progress.Init and Fini will take only 0.001 of second..
The only downside of DelayUserInterface is that it's not applicable
to an operation with very few steps (e.g. 1) that may take a long time.
If a time between Init and the first Update or Fini is really large,
the progress bar will not be visible. }
procedure Init(AMax: Cardinal; const ATitle: string;
const DelayUserInterface: boolean = false);
{ Increments progress bar @link(Position) by @code(StepSize).
Use only when @link(Active), that is between @link(Init) and @link(Fini)
calls.
@link(Position) always stays <= @link(Max) (you can depend on this
when implementaing TProgressUserInterface descendants).
But it is legal to try to raise @link(Position) above
@link(Max) by using this method, we will silently clamp @link(Position)
to @link(Max).
This is usefull when given @link(Max) was only an approximation of needed
steps. }
procedure Step(StepSize: Cardinal = 1);
{ Finish progress bar.
You can call it only when Active = true (that is, if you called Init
before). Fini changes Active to false.
Note that it's perfectly legal to call Fini before Position
reaches Max (it's sensible e.g. when you're allowing user to break
some lenghty operation, or when Max was only an approximation
of steps needed). }
procedure Fini;
constructor Create;
end;
var
{ Global progress bar instance.
Created in initialization of this unit, freed in finalization. }
Progress: TProgress;
type
TProgressNullInterface = class(TProgressUserInterface)
public
procedure Init(Progress: TProgress); override;
procedure Update(Progress: TProgress); override;
procedure Fini(Progress: TProgress); override;
end;
var
{ A special progress user interface, that simply doesn't show progress anywhere.
If you set Progress.UserInterface to this,
then progress Init/Update/Fini will work --- but will not be displayed
anywhere. This is done at the initialization of this unit,
so you can safely use progress bars even before real interface
is initialized.
Created in initialization, freed in finalization. }
ProgressNullInterface: TProgressNullInterface;
implementation
uses Math;
{ TProgressUserInterface ----------------------------------------------------- }
constructor TProgressUserInterface.Create;
begin
inherited;
FBarYPosition := DefaultBarYPosition;
end;
destructor TProgressUserInterface.Destroy;
begin
if OwnsImage then
FreeAndNil(FImage) else
FImage := nil;
inherited;
end;
procedure TProgressUserInterface.SetImage(const Value: TObject);
begin
if FImage <> Value then
begin
if OwnsImage then
FreeAndNil(FImage);
FImage := Value;
end;
end;
{ TProgress ------------------------------------------------------------------ }
procedure TProgress.Init(AMax: Cardinal; const ATitle: string;
const DelayUserInterface: boolean);
begin
Check(not Active, 'TProgress.Init error: progress is already active');
FActive := true;
Check(UserInterface <> nil,
'TProgress.Init error: UserInterface not initialized');
FPosition := 0;
{ Max(AMax, 1) secures us against AMax <= 0 values.
(Otherwise, it would have to be secured at many places when calling
Progress.Init, as sometimes AMax <= 0 values values can naturally
occur. Consider e.g. building octree, when the VRML scene turns out
to be empty.)
The idea is that AMax <= 0 means that actually operation is already
finished. So we'll set Max to 1 (to allow UserInterface to display it,
since user interface can display only Max >= 1 values)
and we'll do Step(1) immediately at the end of TProgress.Init,
to show to user that operation is already done. }
FMax := Math.Max(AMax, 1);
FTitle := ATitle;
{ Calling UserInterface.Init updates LastUpdatePos and LastUpdateTime,
just like calling UserInterface.Update. }
LastUpdatePos := FPosition;
LastUpdateTime := Timer;
UserInterfaceDelayed := DelayUserInterface;
if not UserInterfaceDelayed then
try
UserInterface.Init(Self);
except
{ In case of problems within UserInterface.Init, call Fini
to change our state to not Active. }
Fini;
raise;
end;
{ This means that AMax < Max(AMax, 1), in other words: AMax <= 0.
Then show to user that this operation actually finished. }
try
if AMax < Max then Step;
except
{ In case of problems within UserInterface.Init, call Fini
to change our state to not Active. }
Fini;
raise;
end;
end;
procedure TProgress.Step(StepSize: Cardinal);
begin
Assert(Active, 'TProgress.Step error: progress is not active');
FPosition := FPosition + StepSize;
if Position > Max then FPosition := Max;
if UserInterfaceDelayed then
begin
{ Either actually init user interface, or resign from calling
UserInterface.Update. }
if TimerSeconds(Timer, LastUpdateTime) > UpdateDelay then
begin
UserInterface.Init(Self);
UserInterfaceDelayed := false;
end else
Exit;
end;
if ((Position - LastUpdatePos) / Max > 1 / UpdatePart) and
(TimerSeconds(Timer, LastUpdateTime) > UpdateDelay) then
begin
LastUpdatePos := FPosition;
LastUpdateTime := Timer;
UserInterface.Update(Self);
{$ifdef TESTING_PROGRESS_DELAY}
Sleep(10);
{$endif}
end;
end;
procedure TProgress.SetPosition(const Value: Cardinal);
begin
if Value > Position then
Step(Value - Position);
end;
procedure TProgress.Fini;
begin
Check(Active, 'TProgress.Fini error: progress is not active');
FActive := false;
if not UserInterfaceDelayed then
begin
{ update to reflect the current state of Position, if needed.
Note that this does NOT mean that at the end Position is = Max.
Noone ever guarantees that -- you can call Fini before Position
reaches Max. }
if LastUpdatePos < Position then
UserInterface.Update(Self);
UserInterface.Fini(Self);
end;
end;
constructor TProgress.Create;
begin
inherited;
UpdatePart := DefaultUpdatePart;
UpdateDelay := DefaultUpdateDelay;
FActive := false;
end;
{ TProgressNullInterface ----------------------------------------------------- }
procedure TProgressNullInterface.Init(Progress: TProgress);
begin
end;
procedure TProgressNullInterface.Update(Progress: TProgress);
begin
end;
procedure TProgressNullInterface.Fini(Progress: TProgress);
begin
end;
{ initialization / finalization ---------------------------------------------- }
initialization
Progress := TProgress.Create;
ProgressNullInterface := TProgressNullInterface.Create;
{ initialize Progress.UserInterface to null interface,
this way Progress.Init etc. may be always safely called }
Progress.UserInterface := ProgressNullInterface;
finalization
FreeAndNil(Progress);
FreeAndNil(ProgressNullInterface);
end.
|