{----------------------------------------------------------------------------- The contents of this file are subject to the Mozilla Public License Version 1.1 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.mozilla.org/MPL/MPL-1.1.html Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either expressed or implied. See the License for the specific language governing rights and limitations under the License. The Original Code is: JvBackgrounds.PAS, released on 2004-04-26. The Initial Developer of the Original Code is Robert Rossmair [Robert dott Rossmair att t-online dott de] Portions created by Robert Rossmair are Copyright (C) 2003 Robert Rossmair. All Rights Reserved. Contributors: Andreas Hausladen (ahuser) Peter Thornqvist (peter3) Robert Marquardt (marquardt) Robert Rossmair (rrossmair) You may retrieve the latest version of this file at the Project JEDI's JVCL home page, located at http://jvcl.sourceforge.net Known Issues: -----------------------------------------------------------------------------} // $Id: JvBackgrounds.pas 11024 2006-11-22 18:35:16Z ahuser $ unit JvBackgrounds; {$I jvcl.inc} {$I windowsonly.inc} interface {***************** Conditional Compiler Symbols ************************ USEJVCL JEDI VCL installed (http://sourceforge.net/projects/jvcl/) USE_JvGIF use TGIFImage class from JVCL USE_AM_GIF use GIFImage library by Anders Melander et alii (download address: http://finn.mobilixnet.dk/delphi/). NO_DESIGNHOOK Disables visual feedback in design mode. $DEFINE this if you experience problems in design mode. Such problems might occur if there are other components manipulating the TrrBackgrounds.Client's window procedure. *********************************************************************** } {$DEFINE USEJVCL} {.$DEFINE USE_AM_GIF} {.$DEFINE USE_JvGIF} {$IFDEF USE_JvGIF} {$UNDEF USE_AM_GIF} {$ENDIF USE_JvGIF} uses {$IFDEF USEJVCL} {$IFDEF UNITVERSIONING} JclUnitVersioning, {$ENDIF UNITVERSIONING} {$ENDIF USEJVCL} Windows, Messages, Contnrs, Graphics, Controls, Forms, Classes, JclGraphUtils, JvTypes; type TJvBackgroundMode = (bmTile, bmCenter, bmTopLeft, bmTop, bmTopRight, bmLeft, bmBottomLeft, bmRight, bmBottom, bmBottomRight, bmStretch); EJvBackgroundError = class(EJVCLException); TJvBackgroundShiftMode = (smRows, smColumns); TJvBackgroundImage = class(TPersistent) private FPicture: TPicture; FCanvas: TCanvas; FHorzOffset: Integer; FVertOffset: Integer; FOnChange: TNotifyEvent; FWorkingBmp: TBitmap; FInUpdWorkingBmp: Boolean; FMode: TJvBackgroundMode; FTransparent: Boolean; FTransparentMode: TTransparentMode; FTransparentColor: TColor; FTileWidth: Integer; FTileHeight: Integer; FShift: Integer; FShiftMode: TJvBackgroundShiftMode; FZigZag: Boolean; FAutoSizeTile: Boolean; FFitPictureSize: Boolean; FEnabled: Boolean; FPictureValid: Boolean; FGrayMapped: Boolean; procedure SetGrayMapped(Value: Boolean); procedure SysColorChange; class function MainWindowHook(var Msg: TMessage): Boolean; procedure HookMainWindow; procedure UnhookMainWindow; procedure Changed; function GetTransparentColor: TColor; procedure PictureChanged(Sender: TObject); procedure SetAutoSizeTile(Value: Boolean); procedure SetEnabled(Value: Boolean); procedure SetFitPictureSize(Value: Boolean); procedure SetMode(Value: TJvBackgroundMode); procedure SetPicture(Value: TPicture); procedure SetShift(Value: Integer); procedure SetShiftMode(Value: TJvBackgroundShiftMode); procedure SetTileWidth(Value: Integer); procedure SetTileHeight(Value: Integer); procedure SetTransparent(Value: Boolean); procedure SetTransparentColor(Value: TColor); procedure SetTransparentMode(Value: TTransparentMode); procedure SetZigZag(Value: Boolean); procedure TileGraphic(AClient: TControl; Graphic: TGraphic); function TransparentColorStored: Boolean; procedure UpdateWorkingBmp; procedure WorkingBmpNeeded; protected function HandleWMEraseBkgnd(AClient: TWinControl; var Msg: TMessage): Boolean; function HandleWMPaint(AClient: TWinControl; var Msg: TMessage): Boolean; procedure PaintGraphic(AClient: TControl; DC: HDC; Graphic: TGraphic); property Canvas: TCanvas read FCanvas; property WorkingBmp: TBitmap read FWorkingBmp; public constructor Create; destructor Destroy; override; procedure Assign(Source: TPersistent); override; function DoEraseBackground(AClient: TWinControl; DC: HDC): Boolean; published property AutoSizeTile: Boolean read FAutoSizeTile write SetAutoSizeTile default True; property Enabled: Boolean read FEnabled write SetEnabled default True; property FitPictureSize: Boolean read FFitPictureSize write SetFitPictureSize default False; property GrayMapped: Boolean read FGrayMapped write SetGrayMapped default False; property Mode: TJvBackgroundMode read FMode write SetMode default bmTile; property Picture: TPicture read FPicture write SetPicture; property TileWidth: Integer read FTileWidth write SetTileWidth; property TileHeight: Integer read FTileHeight write SetTileHeight; property Transparent: Boolean read FTransparent write SetTransparent default False; property TransparentColor: TColor read GetTransparentColor write SetTransparentColor stored TransparentColorStored; property TransparentMode: TTransparentMode read FTransparentMode write SetTransparentMode default tmAuto; property Shift: Integer read FShift write SetShift default 0; property ShiftMode: TJvBackgroundShiftMode read FShiftMode write SetShiftMode default smRows; property ZigZag: Boolean read FZigZag write SetZigZag default False; end; TJvControlBackground = class(TJvBackgroundImage) private FClient: TWinControl; public function HookBeforeMessage(var Msg: TMessage): Boolean; procedure HookAfterMessage(var Msg: TMessage); constructor Create(AClient: TWinControl); end; TJvBackground = class; TJvBackgroundClientLink = class(TObject) private FBackground: TJvBackground; FClient: TWinControl; FNewWndProc: Pointer; FPrevWndProc: TFarProc; FClientIsMDIForm: Boolean; procedure ClientInvalidate; procedure MainWndProc(var Msg: TMessage); procedure ClientWndProc(var Message: TMessage); procedure ForceClient(Value: TWinControl; Force: Boolean = True); procedure HookClient; procedure UnhookClient; function GetClientColor: TColor; function GetClientHandle: THandle; procedure SetClient(Value: TWinControl); protected procedure Release; property Background: TJvBackground read FBackground; property ClientColor: TColor read GetClientColor; property ClientHandle: THandle read GetClientHandle; property Client: TWinControl read FClient write SetClient; property ClientIsMDIForm: Boolean read FClientIsMDIForm; public constructor Create(ABackground: TJvBackground; AClient: TWinControl); destructor Destroy; override; end; TJvBackgroundClients = class(TPersistent) private FBackground: TJvBackground; FLinks: TObjectList; FFixups: TStringList; function GetClient(Index: Integer): TWinControl; procedure Invalidate; procedure Notification(AComponent: TComponent; Operation: TOperation); procedure FixupReferences(Root: TComponent); procedure ReadData(Reader: TReader); procedure WriteData(Writer: TWriter); function GetLink(Index: Integer): TJvBackgroundClientLink; protected procedure DefineProperties(Filer: TFiler); override; property Background: TJvBackground read FBackground; property Links[Index: Integer]: TJvBackgroundClientLink read GetLink; public constructor Create(ABackground: TJvBackground); destructor Destroy; override; procedure Clear; procedure Add(Control: TWinControl); procedure Remove(Control: TWinControl); function IndexOf(Control: TWinControl): Integer; property Clients[Index: Integer]: TWinControl read GetClient; default; end; TJvBackground = class(TComponent) private FClients: TJvBackgroundClients; FHandle: HWND; FImage: TJvBackgroundImage; procedure SetClients(Value: TJvBackgroundClients); procedure WallpaperChanged(Sender: TObject); procedure WndProc(var Msg: TMessage); procedure SetImage(const Value: TJvBackgroundImage); protected procedure Loaded; override; procedure Notification(AComponent: TComponent; Operation: TOperation); override; public constructor Create(AOwner: TComponent); override; destructor Destroy; override; function HasClient(Control: TWinControl): Boolean; published property Image: TJvBackgroundImage read FImage write SetImage; property Clients: TJvBackgroundClients read FClients write SetClients; end; procedure GetMappedGrays(var Shades: array of TColor; StartIntensity: Byte); {$IFDEF USEJVCL} {$IFDEF UNITVERSIONING} const UnitVersioning: TUnitVersionInfo = ( RCSfile: '$URL: https://jvcl.svn.sourceforge.net/svnroot/jvcl/tags/JVCL3_32/run/JvBackgrounds.pas $'; Revision: '$Revision: 11024 $'; Date: '$Date: 2006-11-22 19:35:16 +0100 (mer., 22 nov. 2006) $'; LogPath: 'JVCL\run' ); {$ENDIF UNITVERSIONING} {$ENDIF USEJVCL} implementation uses SysUtils, jpeg, StdCtrls, CommCtrl, ComCtrls, Dialogs, {$IFDEF USE_AM_GIF} GIFImage, {$DEFINE HANDLES_GIF} {$ENDIF USE_AM_GIF} {$IFDEF USE_JvGIF} JvGIF, {$DEFINE HANDLES_GIF} {$ENDIF USE_JvGIF} JvJCLUtils, JvConsts, JvResources; type TWinControlAccessProtected = class(TWinControl); {$IFDEF USE_JvGIF} // make TJvGIFImage's Bitmap property visible TGIFImage = class(TJvGIFImage); {$ENDIF USE_JvGIF} const ScrollLineSize = 3; ScrollUnit = 8; CM_RECREATEWINDOW = CM_BASE + 82; CM_RELEASECLIENTLINK = CM_BASE + 83; type TColorGradation = array [Byte] of TColor; PColorGradation = ^TColorGradation; var SysColorGradation: TColorGradation; SysColorGradationInitialized: Boolean = False; Hooked: TList = nil; Backgrounds: TList = nil; procedure UpdateSysColorGradation; var SysHLS: THLSVector; FaceLum, MaxLum: THLSValue; I: Integer; begin SysHLS := RGBtoHLS(ColorToRGB(clBtnHighlight)); MaxLum := SysHLS.Luminance; SysHLS := RGBtoHLS(ColorToRGB(clBtnFace)); FaceLum := SysHLS.Luminance; with SysHLS do begin for I := 0 to 192 do begin Luminance := I * FaceLum div 192; SysColorGradation[I] := HLStoRGB(Hue, Luminance, Saturation); end; for I := 193 to 255 do begin Luminance := FaceLum + (MaxLum - FaceLum) * (I - 192) div (255 - 192); SysColorGradation[I] := HLStoRGB(Hue, Luminance, Saturation); end; end; end; procedure SysColorsNeeded; begin if not SysColorGradationInitialized then begin SysColorGradationInitialized := True; UpdateSysColorGradation; end; end; procedure GetMappedGrays(var Shades: array of TColor; StartIntensity: Byte); var I, Intensity: Integer; begin SysColorsNeeded; Intensity := StartIntensity; for I := Low(Shades) to High(Shades) do begin Shades[I] := SysColorGradation[Intensity]; if Intensity < High(SysColorGradation) then Inc(Intensity); end; end; procedure MapGrays(Dest: TBitmap; Source: TGraphic); var Grays: PColorGradation; I: Integer; SrcWasTransparent: Boolean; begin if Source = nil then Exit; New(Grays); try for I := Low(Grays^) to High(Grays^) do Grays[I] := RGB(I, I, I); with Dest do begin if ((Source is TBitmap) and (TBitmap(Source).PixelFormat in [pf1bit..pf8bit])) {$IFDEF HANDLES_GIF} or (Source is TGIFImage) {$ENDIF} then Assign(Source) else begin PixelFormat := pf8bit; Width := Source.Width; Height := Source.Height; SetBitmapColors(Dest, Grays^, 0); SrcWasTransparent := Source.Transparent; try Source.Transparent := False; Canvas.Draw(0, 0, Source); finally Source.Transparent := SrcWasTransparent; end; end; Handle := CreateMappedBmp(Handle, Grays^, SysColorGradation); end; finally Dispose(Grays); end; end; function TrimmedOffset(Offset, TileDim: Integer): Integer; begin if TileDim <> 0 then if Offset > 0 then Offset := (Offset mod TileDim) - TileDim else if Offset < 0 then Dec(Offset, (Offset div TileDim) * TileDim); Result := Offset; end; function GetClientRect(AClient: TControl): TRect; var MDIClientHandle: HWND; begin if AClient is TCustomForm then begin MDIClientHandle := TForm(AClient).ClientHandle; if MDIClientHandle <> 0 then begin Windows.GetClientRect(MDIClientHandle, Result); Exit; end; end; Result := AClient.ClientRect; end; function GetVirtualClientRect(AClient: TControl): TRect; var ClientHandle: HWND; ScrollInfo: TScrollInfo; R: TRect; TVTopItem: TTreeNode; begin Result := GetClientRect(AClient); if AClient is TWinControl then begin ClientHandle := TWinControl(AClient).Handle; FillChar(ScrollInfo, SizeOf(ScrollInfo), 0); ScrollInfo.cbSize := SizeOf(ScrollInfo); ScrollInfo.fMask := SIF_ALL; GetScrollInfo(ClientHandle, SB_HORZ, ScrollInfo); if ScrollInfo.nPage > 0 then // horizontal scroll bar visible begin if ScrollInfo.nMax > Result.Right then Result.Right := ScrollInfo.nMax; Dec(Result.Left, ScrollInfo.nPos); Dec(Result.Right, ScrollInfo.nPos); end; GetScrollInfo(ClientHandle, SB_VERT, ScrollInfo); if ScrollInfo.nPage > 0 then // vertical scroll bar visible begin if AClient is TCustomListBox then with TListBox(AClient) do begin ScrollInfo.nPos := ScrollInfo.nPos * ItemHeight; ScrollInfo.nMax := ScrollInfo.nMax * ItemHeight; end else if AClient is TCustomTreeView then begin TVTopItem := TCustomTreeView(AClient).TopItem; if Assigned(TVTopItem) and TreeView_GetItemRect(ClientHandle, TVTopItem.ItemID, R, False) then begin ScrollInfo.nPos := ScrollInfo.nPos * R.Bottom; ScrollInfo.nMax := ScrollInfo.nMax * R.Bottom; end; end; if ScrollInfo.nMax > Result.Bottom then Result.Bottom := ScrollInfo.nMax; Dec(Result.Top, ScrollInfo.nPos); Dec(Result.Bottom, ScrollInfo.nPos); end; end; end; function GetClientBrush(AClient: TControl): TBrush; begin if AClient is TWinControl then Result := TWinControl(AClient).Brush else Result := AClient.Parent.Brush; end; function IsMDIForm(Control: TControl): Boolean; begin Result := False; if Assigned(Control) then if Control is TCustomForm then Result := TForm(Control).FormStyle = fsMDIForm; end; //=== { TJvBackgroundImage } ================================================= constructor TJvBackgroundImage.Create; begin inherited Create; FCanvas := TCanvas.Create; FAutoSizeTile := True; FEnabled := True; FTransparentColor := clDefault; FPicture := TPicture.Create; FPicture.OnChange := PictureChanged; HookMainWindow; end; destructor TJvBackgroundImage.Destroy; begin UnhookMainWindow; FPicture.Free; FWorkingBmp.Free; FCanvas.Free; inherited Destroy; end; procedure TJvBackgroundImage.Assign(Source: TPersistent); var Src: TJvBackgroundImage; begin if Source is TJvBackgroundImage then begin Src := TJvBackgroundImage(Source); AutoSizeTile := Src.AutoSizeTile; Enabled := Src.Enabled; FitPictureSize := Src.FitPictureSize; GrayMapped := Src.GrayMapped; Mode := Src.Mode; Picture := Src.Picture; TileWidth := Src.TileWidth; TileHeight := Src.TileHeight; Transparent := Src.Transparent; TransparentColor := Src.TransparentColor; TransparentMode := Src.TransparentMode; Shift := Src.Shift; ShiftMode := Src.ShiftMode; ZigZag := Src.ZigZag; end else inherited Assign(Source); end; procedure TJvBackgroundImage.Changed; begin if Assigned(FOnChange) then FOnChange(Self); end; function TJvBackgroundImage.HandleWMEraseBkgnd(AClient: TWinControl; var Msg: TMessage): Boolean; begin Result := FEnabled and FPictureValid; if Result then begin if not IsIconic(AClient.Handle) then if not TWinControlAccessProtected(AClient).FDoubleBuffered or (Msg.wParam = Msg.lParam) then DoEraseBackground(AClient, TWMEraseBkgnd(Msg).DC); Msg.Result := 1; end; end; function TJvBackgroundImage.HandleWMPaint(AClient: TWinControl; var Msg: TMessage): Boolean; var DC, MemDC: HDC; MemBitmap, OldBitmap: HBITMAP; PS: TPaintStruct; ClientRect: TRect; begin Result := False; if FEnabled and FPictureValid then if TWinControlAccessProtected(AClient).FDoubleBuffered and (TWMPaint(Msg).DC = 0) then begin DC := GetDC(HWND_DESKTOP); ClientRect := AClient.ClientRect; MemBitmap := CreateCompatibleBitmap(DC, ClientRect.Right, ClientRect.Bottom); ReleaseDC(HWND_DESKTOP, DC); MemDC := CreateScreenCompatibleDC; OldBitmap := SelectObject(MemDC, MemBitmap); try DC := BeginPaint(AClient.Handle, PS); DoEraseBackground(AClient, MemDC); Msg.Result := AClient.Perform(WM_PAINT, MemDC, 0); BitBlt(DC, 0, 0, ClientRect.Right, ClientRect.Bottom, MemDC, 0, 0, SRCCOPY); EndPaint(AClient.Handle, PS); finally SelectObject(MemDC, OldBitmap); DeleteDC(MemDC); DeleteObject(MemBitmap); end; Result := True; end; end; procedure TJvBackgroundImage.TileGraphic(AClient: TControl; Graphic: TGraphic); var I, J: Integer; iMin: Integer; FirstVisibleRow, S, OddShift: Integer; Left, Top, Width, Height: Integer; HorzOffset, VertOffset: Integer; begin with GetClientRect(AClient) do begin Width := Right; Height := Bottom; end; if IsMDIForm(AClient) then begin HorzOffset := FHorzOffset; VertOffset := FVertOffset; end else with GetVirtualClientRect(AClient) do begin HorzOffset := Left; VertOffset := Top; end; if FShiftMode = smRows then begin FirstVisibleRow := -VertOffset div FTileHeight; if VertOffset > 0 then Dec(FirstVisibleRow); end else begin FirstVisibleRow := -HorzOffset div FTileWidth; if HorzOffset > 0 then Dec(FirstVisibleRow); end; Left := TrimmedOffset(HorzOffset, FTileWidth); Top := TrimmedOffset(VertOffset, FTileHeight); Dec(Width, Left); Dec(Height, Top); OddShift := 0; // just to satisfy the compiler if FShiftMode = smRows then begin if FZigZag then begin OddShift := FTileWidth div 2; if Odd(FirstVisibleRow) then S := OddShift else S := 0; end else begin S := (FirstVisibleRow * FShift) mod FTileWidth; if S < 0 then Inc(S, FTileWidth); end; for J := 0 to (Height - 1) div FTileHeight do begin if S = 0 then iMin := 0 else iMin := -1; for I := iMin to (Width - 1) div FTileWidth do Canvas.Draw(Left + I * FTileWidth + S, Top + J * FTileHeight, Graphic); if FZigZag then S := S xor OddShift else begin Inc(S, FShift); S := S mod FTileWidth; end; end; end else begin if FZigZag then begin OddShift := FTileHeight div 2; if Odd(FirstVisibleRow) then S := OddShift else S := 0; end else begin S := (FirstVisibleRow * FShift) mod FTileHeight; if S < 0 then Inc(S, FTileHeight); end; for I := 0 to (Width - 1) div FTileWidth do begin if S = 0 then iMin := 0 else iMin := -1; for J := iMin to (Height - 1) div FTileHeight do Canvas.Draw(Left + I * FTileWidth, Top + J * FTileHeight + S, Graphic); if FZigZag then S := S xor OddShift else begin Inc(S, FShift); S := S mod FTileHeight; end; end; end; end; procedure TJvBackgroundImage.PaintGraphic(AClient: TControl; DC: HDC; Graphic: TGraphic); var R, Rg: TRect; X, Y, W, H: Integer; SaveIndex: Integer; WindowStyle: DWORD; GraphW, GraphH: Integer; Factor, FactorVert: Single; begin SaveIndex := SaveDC(DC); with Canvas do begin Handle := DC; if FMode = bmTile then TileGraphic(AClient, Graphic) else begin if IsMDIForm(AClient) then begin R := GetClientRect(AClient); // We don't want the background move // when scrollbars appear or disappear: WindowStyle := GetWindowLong(TForm(AClient).ClientHandle, GWL_STYLE); if (WindowStyle and WS_HSCROLL) <> 0 then Inc(R.Bottom, GetSystemMetrics(SM_CYHSCROLL)); if (WindowStyle and WS_VSCROLL) <> 0 then Inc(R.Right, GetSystemMetrics(SM_CXVSCROLL)); end else R := GetVirtualClientRect(AClient); W := R.Right - R.Left; H := R.Bottom - R.Top; GraphW := Graphic.Width; GraphH := Graphic.Height; if FFitPictureSize and not (FMode = bmStretch) then begin Factor := W / GraphW; FactorVert := H / GraphH; if FactorVert < Factor then Factor := FactorVert; GraphW := Round(Factor * GraphW); GraphH := Round(Factor * GraphH); end; Rg := Rect(0, 0, GraphW, GraphH); Brush := GetClientBrush(AClient); case FMode of bmCenter: begin X := R.Left + (W - GraphW) div 2; Y := R.Top + (H - GraphH) div 2; FillRect(Rect(R.Left, R.Top, R.Right, Y)); FillRect(Rect(R.Left, Y, X, Y + GraphH)); FillRect(Rect(X + GraphW, Y, R.Right, Y + GraphH)); FillRect(Rect(R.Left, Y + GraphH, R.Right, R.Bottom)); OffsetRect(Rg, X, Y); end; bmStretch: Rg := R; bmTopLeft: begin FillRect(Rect(R.Left + GraphW, R.Top, R.Right, R.Top + GraphH)); FillRect(Rect(R.Left, R.Top + GraphH, R.Right, R.Bottom)); OffsetRect(Rg, R.Left, R.Top); end; bmTopRight: begin FillRect(Rect(R.Left, R.Top, R.Right - GraphW, R.Top + GraphH)); FillRect(Rect(R.Left, R.Top + GraphH, R.Right, R.Bottom)); OffsetRect(Rg, R.Right - GraphW, R.Top); end; bmBottomLeft: begin FillRect(Rect(R.Left, R.Top, R.Right, R.Bottom - GraphH)); FillRect(Rect(R.Left + GraphW, R.Bottom - GraphH, R.Right, R.Bottom)); OffsetRect(Rg, R.Left, R.Bottom - GraphH); end; bmBottomRight: begin FillRect(Rect(R.Left, R.Top, R.Right, R.Bottom - GraphH)); FillRect(Rect(R.Left, R.Bottom - GraphH, R.Right - GraphW, R.Bottom)); OffsetRect(Rg, R.Right - GraphW, R.Bottom - GraphH); end; bmTop: begin X := R.Left + (W - GraphW) div 2; FillRect(Rect(R.Left, R.Top, X, GraphH)); FillRect(Rect(X + GraphW, R.Top, R.Right, GraphH)); FillRect(Rect(R.Left, R.Top + GraphH, R.Right, R.Bottom)); OffsetRect(Rg, X, R.Top); end; bmLeft: begin Y := R.Top + (H - GraphH) div 2; FillRect(Rect(R.Left, R.Top, R.Right, Y)); FillRect(Rect(R.Left + GraphW, Y, R.Right, Y + GraphH)); FillRect(Rect(R.Left, Y + GraphH, R.Right, R.Bottom)); OffsetRect(Rg, R.Left, Y); end; bmBottom: begin X := R.Left + (W - GraphW) div 2; Y := R.Bottom - GraphH; FillRect(Rect(R.Left, R.Top, R.Right, Y)); FillRect(Rect(R.Left, Y, X, R.Bottom)); FillRect(Rect(X + GraphW, Y, R.Right, R.Bottom)); OffsetRect(Rg, X, Y); end; bmRight: begin X := R.Right - GraphW; Y := R.Top + (H - GraphH) div 2; FillRect(Rect(R.Left, R.Top, R.Right, Y)); FillRect(Rect(R.Left, Y, X, Y + GraphH)); FillRect(Rect(R.Left, Y + GraphH, R.Right, R.Bottom)); OffsetRect(Rg, X, Y); end; end; StretchDraw(Rg, Graphic); end; Handle := 0; end; RestoreDC(DC, SaveIndex); end; function TJvBackgroundImage.DoEraseBackground(AClient: TWinControl; DC: HDC): Boolean; var Graphic: TGraphic; Bmp: TBitmap; begin Result := FPictureValid and AClient.HandleAllocated; if Result then begin Bmp := nil; try Graphic := FWorkingBmp; if Graphic = nil then Graphic := FPicture.Graphic else if Transparent then begin Bmp := TBitmap.Create; Bmp.Assign(Graphic); Bmp.Canvas.Brush := GetClientBrush(AClient); Bmp.Canvas.FillRect(Rect(0, 0, Bmp.Width, Bmp.Height)); Bmp.Canvas.Draw(0, 0, Graphic); Bmp.Transparent := False; Graphic := Bmp; end; PaintGraphic(AClient, DC, Graphic); finally Bmp.Free; end; end; end; function TJvBackgroundImage.GetTransparentColor: TColor; var Bmp: TBitmap; begin Bmp := nil; if FTransparentColor = clDefault then {$IFDEF HANDLES_GIF} if FPicture.Graphic is TGIFImage then Bmp := TGIFImage(FPicture.Graphic).Bitmap else {$ENDIF HANDLES_GIF} if FPicture.Graphic is TBitmap then Bmp := TBitmap(FPicture.Graphic); if Assigned(Bmp) then begin if Bmp.Monochrome then Result := clWhite else Result := Bmp.Canvas.Pixels[0, Bmp.Height - 1]; end else Result := ColorToRGB(FTransparentColor); Result := Result or $02000000; end; procedure TJvBackgroundImage.PictureChanged(Sender: TObject); begin if FInUpdWorkingBmp then Exit; FPictureValid := (FPicture.Width > 0) and (FPicture.Height > 0); if (FTileWidth < Picture.Width) or (FTileHeight < Picture.Height) or (AutoSizeTile and FPictureValid) then begin FTileWidth := Picture.Width; FTileHeight := Picture.Height; end; with Picture do if Graphic <> nil then Graphic.Transparent := FTransparent; UpdateWorkingBmp; end; procedure TJvBackgroundImage.SetAutoSizeTile(Value: Boolean); begin if FAutoSizeTile <> Value then begin FAutoSizeTile := Value; if Mode = bmTile then if (TileWidth <> Picture.Width) or (TileHeight <> Picture.Height) then PictureChanged(Self); end; end; procedure TJvBackgroundImage.SetEnabled(Value: Boolean); begin if Value <> FEnabled then begin FEnabled := Value; Changed; end; end; procedure TJvBackgroundImage.SetFitPictureSize(Value: Boolean); begin if FFitPictureSize <> Value then begin FFitPictureSize := Value; if not (FMode in [bmTile, bmStretch]) then Changed; end; end; procedure TJvBackgroundImage.SetMode(Value: TJvBackgroundMode); var TileModeChanged: Boolean; begin if Value <> FMode then begin TileModeChanged := (FMode = bmTile) or (Value = bmTile); FMode := Value; if TileModeChanged and ((FTileWidth <> Picture.Width) or (FTileHeight <> Picture.Height)) then PictureChanged(Self) else Changed; end; end; procedure TJvBackgroundImage.SetPicture(Value: TPicture); begin FPicture.Assign(Value); end; procedure TJvBackgroundImage.SetShift(Value: Integer); begin if Value <> FShift then begin FShift := Value; FZigZag := False; if FMode = bmTile then Changed; end; end; procedure TJvBackgroundImage.SetShiftMode(Value: TJvBackgroundShiftMode); begin if FShiftMode <> Value then begin FShiftMode := Value; if FMode = bmTile then Changed; end; end; procedure TJvBackgroundImage.SetTileWidth(Value: Integer); begin if AutoSizeTile then Exit; if Value < Picture.Width then Value := Picture.Width; if Value <> FTileWidth then begin FTileWidth := Value; if Mode = bmTile then PictureChanged(Self); end; end; procedure TJvBackgroundImage.SetTileHeight(Value: Integer); begin if AutoSizeTile then Exit; if Value < Picture.Height then Value := Picture.Height; if Value <> FTileHeight then begin FTileHeight := Value; if Mode = bmTile then PictureChanged(Self); end; end; procedure TJvBackgroundImage.SetTransparent(Value: Boolean); begin if Value <> FTransparent then begin FTransparent := Value; PictureChanged(Self); end; end; procedure TJvBackgroundImage.SetTransparentColor(Value: TColor); begin if Value <> FTransparentColor then begin if Value = clDefault then FTransparentMode := tmAuto else FTransparentMode := tmFixed; FTransparentColor := Value; if Transparent then UpdateWorkingBmp; end; end; procedure TJvBackgroundImage.SetTransparentMode(Value: TTransparentMode); begin if Value <> FTransparentMode then begin if Value = tmAuto then SetTransparentColor(clDefault) else SetTransparentColor(GetTransparentColor); end; end; procedure TJvBackgroundImage.SetZigZag(Value: Boolean); begin if Value <> FZigZag then begin FZigZag := Value; if FMode = bmTile then Changed; end; end; function TJvBackgroundImage.TransparentColorStored: Boolean; begin Result := FTransparentMode = tmFixed; end; { TJvBackgroundImage.UpdateWorkingBmp Transparency: all except TJPEGImage GrayMapping: all except TIcon, TMetafile } procedure TJvBackgroundImage.UpdateWorkingBmp; var X, Y: Integer; IsBitmap: Boolean; Bmp: TBitmap; MaskBmp: TBitmap; {$IFNDEF NO_JPEG} GrayscaleState: Boolean; {$ENDIF !NO_JPEG} {$IFNDEF NO_JPEG} IsJPEG: Boolean; {$ENDIF !NO_JPEG} IsTransparent: Boolean; IsTranspGraphic: Boolean; IsIcon: Boolean; SizeTailored: Boolean; procedure DrawGraphic(Graphic: TGraphic); begin with FWorkingBmp.Canvas do begin Brush.Color := TransparentColor; FillRect(Rect(0, 0, FTileWidth, FTileHeight)); Draw(X, Y, Graphic); end; end; function CreateTransparentBmp(Graphic: TGraphic): TBitmap; var W, H: Integer; begin Result := TBitmap.Create; if IsBitmap then Result.Assign(Graphic) else begin W := Graphic.Width; H := Graphic.Height; Result.Width := W; Result.Height := H; with Result.Canvas do begin Brush.Color := TransparentColor; FillRect(Rect(0, 0, W, H)); Draw(0, 0, Graphic); end; end; end; begin if FInUpdWorkingBmp then Exit; with FPicture do if Graphic <> nil then try FInUpdWorkingBmp := True; SizeTailored := False; X := 0; Y := 0; if FMode = bmTile then begin X := FTileWidth - Graphic.Width; Y := FTileHeight - Graphic.Height; SizeTailored := (X <> 0) or (Y <> 0); X := X div 2; Y := Y div 2; end; IsBitmap := (Graphic is TBitmap) // GIF goes as bitmap here {$IFDEF HANDLES_GIF} or (Graphic is TGIFImage) {$ENDIF}; IsIcon := Graphic is TIcon; IsTranspGraphic := IsIcon or (Graphic is TMetafile); // if Graphic is transparent {$IFDEF NO_JPEG} IsTransparent := Transparent or IsTranspGraphic; {$ELSE} IsJPEG := Graphic is TJPEGImage; IsTransparent := (Transparent and not IsJPEG) or IsTranspGraphic; {$ENDIF NO_JPEG} if IsTransparent or FGrayMapped or SizeTailored then begin WorkingBmpNeeded; if IsTranspGraphic then with FWorkingBmp.Canvas do begin Brush.Color := TransparentColor; FillRect(Rect(0, 0, FTileWidth, FTileHeight)); Draw(X, Y, Graphic); end else if IsTransparent then // and not IsTranspGraphic begin Bmp := CreateTransparentBmp(Graphic); try with TImageList.CreateSize(Graphic.Width, Graphic.Height) do try if FGrayMapped then begin MaskBmp := TBitmap.Create; with MaskBmp do try Assign(Bmp); Mask(GetTransparentColor); MapGrays(Bmp, FPicture.Graphic); Add(Bmp, MaskBmp); finally Free; end; end else AddMasked(Bmp, GetTransparentColor); FWorkingBmp.HandleType := bmDDB; // otherwise eventually background color won't appear correctly with FWorkingBmp.Canvas do begin Brush.Color := TransparentColor; FillRect(Rect(0, 0, FTileWidth, FTileHeight)); end; BkColor := ColorToRGB(TransparentColor); Draw(FWorkingBmp.Canvas, X, Y, 0); finally Free; end finally Bmp.Free; end end else if GrayMapped then // and not Transparent begin Bmp := TBitmap.Create; try {$IFNDEF NO_JPEG} if IsJPEG then with TJPEGImage(Graphic) do begin GrayscaleState := Grayscale; try Grayscale := True; Bmp.Assign(Graphic); finally Grayscale := GrayscaleState; end; end; {$ENDIF !NO_JPEG} MapGrays(Bmp, FPicture.Graphic); DrawGraphic(Bmp); finally Bmp.Free; end end else // if SizeTailored DrawGraphic(Picture.Graphic); WorkingBmp.Transparent := Transparent; WorkingBmp.TransparentColor := TransparentColor; Changed; Exit; end; finally FInUpdWorkingBmp := False; end; FWorkingBmp.Free; FWorkingBmp := nil; Changed; end; procedure TJvBackgroundImage.WorkingBmpNeeded; var W, H: Integer; begin if FWorkingBmp = nil then FWorkingBmp := TBitmap.Create; if FMode = bmTile then begin W := FTileWidth; H := FTileHeight; end else begin W := FPicture.Graphic.Width; H := FPicture.Graphic.Height; end; FWorkingBmp.Width := W; FWorkingBmp.Height := H; end; class function TJvBackgroundImage.MainWindowHook(var Msg: TMessage): Boolean; var I: Integer; begin Result := False; if Msg.Msg = WM_SYSCOLORCHANGE then begin UpdateSysColorGradation; for I := 0 to Hooked.Count - 1 do TJvBackgroundImage(Hooked[I]).SysColorChange; end; end; procedure TJvBackgroundImage.HookMainWindow; begin if Hooked = nil then begin Hooked := TList.Create; Application.HookMainWindow(MainWindowHook); end; if Hooked.IndexOf(Self) = -1 then Hooked.Add(Self); end; procedure TJvBackgroundImage.UnhookMainWindow; begin Hooked.Remove(Self); if Hooked.Count = 0 then begin Application.UnhookMainWindow(MainWindowHook); Hooked.Free; Hooked := nil; end; end; procedure TJvBackgroundImage.SysColorChange; begin if FGrayMapped then UpdateWorkingBmp; end; procedure TJvBackgroundImage.SetGrayMapped(Value: Boolean); begin if Value <> FGrayMapped then begin if Value then SysColorsNeeded; FGrayMapped := Value; UpdateWorkingBmp; end; end; //=== { TJvControlBackground } =============================================== constructor TJvControlBackground.Create(AClient: TWinControl); begin inherited Create; FClient := AClient; end; function TJvControlBackground.HookBeforeMessage(var Msg: TMessage): Boolean; begin Result := False; if FEnabled then case Msg.Msg of WM_PAINT: Result := HandleWMPaint(FClient, Msg); WM_ERASEBKGND: Result := HandleWMEraseBkgnd(FClient, Msg); end; end; procedure TJvControlBackground.HookAfterMessage(var Msg: TMessage); begin if FEnabled then case Msg.Msg of WM_SIZE: if not (FMode in [bmTile, bmTopLeft]) then FClient.Invalidate; WM_HSCROLL: if FMode <> bmTile then FClient.Invalidate; WM_VSCROLL: if FMode <> bmTile then FClient.Invalidate; end; end; //=== { TJvBackgroundClientLink } ============================================ constructor TJvBackgroundClientLink.Create(ABackground: TJvBackground; AClient: TWinControl); begin inherited Create; FBackground := ABackground; FNewWndProc := MakeObjectInstance(MainWndProc); ForceClient(AClient); ClientInvalidate; end; destructor TJvBackgroundClientLink.Destroy; begin UnhookClient; if Assigned(FNewWndProc) then FreeObjectInstance(FNewWndProc); inherited Destroy; end; procedure TJvBackgroundClientLink.ClientInvalidate; begin if not (csReading in FBackground.ComponentState) and not (csDestroying in FClient.ComponentState) then InvalidateRect(ClientHandle, nil, True); end; function GetMDIClientScrollDelta(ClientHandle: HWND; ScrollBar: Integer; const Msg: TWMScroll): Integer; var ScrollInfo: TScrollInfo; Delta, MaxChange: Integer; begin ScrollInfo.cbSize := SizeOf(ScrollInfo); ScrollInfo.fMask := SIF_ALL; GetScrollInfo(ClientHandle, ScrollBar, ScrollInfo); Delta := 0; case Msg.ScrollCode of SB_LINELEFT: begin Delta := ScrollInfo.nPos - ScrollInfo.nMin; if Delta > ScrollLineSize then Delta := ScrollLineSize; end; SB_LINERIGHT: with ScrollInfo do begin Delta := nPage - 1; if Delta < 0 then Delta := 0; Delta := nPos - (nMax - Delta); if Delta < -ScrollLineSize then Delta := -ScrollLineSize; end; SB_PAGELEFT: with ScrollInfo do begin Delta := nPage - 1; if Delta < 0 then Delta := 0; if Delta > nPos - nMin then Delta := nPos - nMin; end; SB_PAGERIGHT: with ScrollInfo do begin Delta := nPage - 1; if Delta < 0 then Delta := 0; MaxChange := (nMax - Delta) - nPos; if Delta > MaxChange then Delta := MaxChange; Delta := -Delta; end; SB_THUMBPOSITION: Delta := -Msg.Pos; end; Result := Delta * ScrollUnit; end; procedure TJvBackgroundClientLink.ClientWndProc(var Message: TMessage); procedure InvalidateBackground; begin InvalidateRect(ClientHandle, nil, True); end; begin if ClientHandle <> 0 then with FBackground.FImage, Message do begin if ClientIsMDIForm then begin if Msg = WM_ERASEBKGND then if FEnabled and DoEraseBackground(FClient, TWMEraseBkgnd(Message).DC) then begin Result := 1; Exit; end; end else // not ClientIsMDIForm begin if FEnabled then case Msg of WM_PAINT: if HandleWMPaint(FClient, Message) then Exit; WM_ERASEBKGND: if HandleWMEraseBkgnd(FClient, Message) then Exit; end; Result := CallWindowProc(FPrevWndProc, ClientHandle, Msg, wParam, lParam); if Msg = CM_RELEASE then Exit; end; case Msg of WM_DESTROY: begin UnhookClient; if not (csDestroying in FClient.ComponentState) then PostMessage(FBackground.FHandle, CM_RECREATEWINDOW, 0, Longint(Self)); end; WM_SIZE: if not (FMode in [bmTile, bmTopLeft]) then InvalidateBackground; WM_HSCROLL: begin if ClientIsMDIForm then Inc(FHorzOffset, GetMDIClientScrollDelta(ClientHandle, SB_HORZ, TWMScroll(Message))); if FMode <> bmTile then InvalidateBackground; end; WM_VSCROLL: begin if ClientIsMDIForm then Inc(FVertOffset, GetMDIClientScrollDelta(ClientHandle, SB_VERT, TWMScroll(Message))); if FMode <> bmTile then InvalidateBackground; end; end; if ClientIsMDIForm then Result := CallWindowProc(FPrevWndProc, ClientHandle, Msg, wParam, lParam); end; end; procedure TJvBackgroundClientLink.MainWndProc(var Msg: TMessage); begin try try ClientWndProc(Msg); finally //FreeDeviceContexts; FreeMemoryContexts; end; except Application.HandleException(Self); end; end; procedure TJvBackgroundClientLink.ForceClient(Value: TWinControl; Force: Boolean = True); var I: Integer; Bk: TJvBackground; begin if Value <> FClient then begin for I := 0 to Backgrounds.Count - 1 do begin Bk := Backgrounds[I]; if (Bk <> FBackground) and Bk.HasClient(Value) then if Force then begin Bk.Clients.Remove(Value); Break; end else Exit; end; UnhookClient; if Assigned(FClient) then FBackground.RemoveFreeNotification(FClient); FClient := Value; if Assigned(Value) then begin FClientIsMDIForm := IsMDIForm(Value); FBackground.FreeNotification(Value); if not (csLoading in FBackground.ComponentState) then HookClient; end; end; end; procedure TJvBackgroundClientLink.HookClient; begin {$IFDEF NO_DESIGNHOOK} if csDesigning in ComponentState then Exit; {$ENDIF NO_DESIGNHOOK} if Assigned(FClient) and not Assigned(FPrevWndProc) then if not ((csLoading in FClient.ComponentState) or ((FClient is TCustomForm) and (csDesigning in FClient.ComponentState))) then begin FClient.HandleNeeded; FPrevWndProc := Pointer(SetWindowLong(ClientHandle, GWL_WNDPROC, Longint(FNewWndProc))); FBackground.FImage.UpdateWorkingBmp; end; end; procedure TJvBackgroundClientLink.UnhookClient; const WorkaroundStr: array [Boolean] of string = ('', SWorkaround); begin if Assigned(FPrevWndProc) then if Assigned(FClient) then begin if FClient.HandleAllocated then begin if (Longint(FNewWndProc) <> SetWindowLong(ClientHandle, GWL_WNDPROC, Longint(FPrevWndProc))) and not (csDestroying in FClient.ComponentState) then MessageDlg(Format(SChainError, [FBackground.Owner.Name, FBackground.Name, FClient.Name, WorkaroundStr[csDesigning in FBackground.ComponentState]]), mtWarning, [mbOK], 0); end; FPrevWndProc := nil; ClientInvalidate; FClientIsMDIForm := False; end; end; function TJvBackgroundClientLink.GetClientColor: TColor; begin Result := TWinControlAccessProtected(FClient).Color; end; function TJvBackgroundClientLink.GetClientHandle: THandle; begin Result := 0; if FClient is TCustomForm then Result := TForm(FClient).ClientHandle; if Result = 0 then if FClient.HandleAllocated then Result := FClient.Handle; end; procedure TJvBackgroundClientLink.SetClient(Value: TWinControl); begin ForceClient(Value); end; procedure TJvBackgroundClientLink.Release; begin UnhookClient; PostMessage(FBackground.FHandle, CM_RELEASECLIENTLINK, 0, Longint(Self)); end; //=== { TJvBackgroundClients } =============================================== constructor TJvBackgroundClients.Create(ABackground: TJvBackground); begin inherited Create; FBackground := ABackground; FLinks := TObjectList.Create; FLinks.OwnsObjects := False; FFixups := TStringList.Create; end; destructor TJvBackgroundClients.Destroy; begin FFixups.Free; Clear; // release links FLinks.Free; inherited Destroy; end; procedure TJvBackgroundClients.Clear; var I: Integer; begin for I := 0 to FLinks.Count - 1 do Links[I].Release; FLinks.Clear; end; procedure TJvBackgroundClients.Add(Control: TWinControl); begin if IndexOf(Control) < 0 then FLinks.Add(TJvBackgroundClientLink.Create(FBackground, Control)); end; procedure TJvBackgroundClients.Remove(Control: TWinControl); var I: Integer; Link: TJvBackgroundClientLink; begin I := IndexOf(Control); if I >= 0 then begin Link := TJvBackgroundClientLink(Links[I]); FLinks.Delete(I); Link.Release; end; end; function TJvBackgroundClients.GetClient(Index: Integer): TWinControl; begin Result := TJvBackgroundClientLink(FLinks[Index]).Client; end; function TJvBackgroundClients.IndexOf(Control: TWinControl): Integer; var I: Integer; begin Result := -1; for I := 0 to FLinks.Count - 1 do if Links[I].Client = Control then begin Result := I; Exit; end; end; procedure TJvBackgroundClients.Notification(AComponent: TComponent; Operation: TOperation); var I: Integer; Client: TWinControl; begin if Operation = opRemove then for I := 0 to FLinks.Count - 1 do begin Client := Links[I].Client; if AComponent = Client then Remove(Client); end; end; procedure TJvBackgroundClients.DefineProperties(Filer: TFiler); function WriteClients: Boolean; var I: Integer; AncestorClients: TJvBackgroundClients; begin AncestorClients := TJvBackgroundClients(Filer.Ancestor); if AncestorClients = nil then Result := True // FLinks.Count > 0 else if AncestorClients.FLinks.Count <> FLinks.Count then Result := True else begin Result := False; for I := 0 to FLinks.Count - 1 do begin Result := not (Clients[I] = AncestorClients[I]); if Result then Break; end end; end; begin inherited DefineProperties(Filer); Filer.DefineProperty('Clients', ReadData, WriteData, WriteClients); end; procedure TJvBackgroundClients.ReadData(Reader: TReader); begin Reader.ReadListBegin; while not Reader.EndOfList do FFixups.Add(Reader.ReadString); Reader.ReadListEnd; end; procedure TJvBackgroundClients.WriteData(Writer: TWriter); var I: Integer; begin Writer.WriteListBegin; for I := 0 to FLinks.Count - 1 do Writer.WriteString(Clients[I].Name); Writer.WriteListEnd; end; procedure TJvBackgroundClients.FixupReferences(Root: TComponent); var I: Integer; S: string; NextItem: TComponent; begin FLinks.Clear; with FFixups do begin FLinks.Capacity := Capacity; for I := 0 to Count - 1 do begin S := Strings[I]; if Root.Name = S then NextItem := Root else NextItem := Root.FindComponent(Strings[I]); if NextItem = nil then Break; if NextItem is TWinControl then Self.Add(TWinControl(NextItem)); end; end; end; function TJvBackgroundClients.GetLink(Index: Integer): TJvBackgroundClientLink; begin Result := TJvBackgroundClientLink(FLinks[Index]); end; procedure TJvBackgroundClients.Invalidate; var I: Integer; begin for I := 0 to FLinks.Count - 1 do Links[I].ClientInvalidate; end; //=== { TJvBackground } ====================================================== var Registered: Boolean = False; constructor TJvBackground.Create(AOwner: TComponent); begin inherited Create(AOwner); FHandle := AllocateHWnd(WndProc); FImage := TJvBackgroundImage.Create; FImage.FOnChange := WallpaperChanged; if Backgrounds = nil then Backgrounds := TList.Create; Backgrounds.Add(Self); FClients := TJvBackgroundClients.Create(Self); if csDesigning in ComponentState then if Assigned(Owner) then if Owner is TWinControl then FClients.Add(TWinControl(Owner)); if not Registered then begin Classes.RegisterClasses([TJvBackgroundImage]); Registered := True; end; end; destructor TJvBackground.Destroy; begin DeallocateHWnd(FHandle); FClients.Free; Backgrounds.Remove(Self); FImage.Free; inherited Destroy; end; procedure TJvBackground.Loaded; begin inherited Loaded; FClients.FixupReferences(Owner); end; procedure TJvBackground.Notification(AComponent: TComponent; Operation: TOperation); begin if not (csDestroying in ComponentState) and Assigned(FClients) then FClients.Notification(AComponent, Operation); inherited Notification(AComponent, Operation); end; procedure TJvBackground.SetClients(Value: TJvBackgroundClients); begin // dummy method to make Clients property visible in Object Inspector end; procedure TJvBackground.WallpaperChanged; begin Clients.Invalidate; end; procedure TJvBackground.WndProc(var Msg: TMessage); begin try case Msg.Msg of CM_RECREATEWINDOW: TJvBackgroundClientLink(Msg.lParam).HookClient; CM_RELEASECLIENTLINK: TJvBackgroundClientLink(Msg.lParam).Free; else Msg.Result := DefWindowProc(FHandle, Msg.Msg, Msg.wParam, Msg.lParam); end; except Application.HandleException(Self); end; end; procedure TJvBackground.SetImage(const Value: TJvBackgroundImage); begin FImage.Assign(Value); end; function TJvBackground.HasClient(Control: TWinControl): Boolean; begin Result := Clients.IndexOf(Control) >= 0; end; initialization {$IFDEF UNITVERSIONING} RegisterUnitVersion(HInstance, UnitVersioning); {$ENDIF UNITVERSIONING} finalization FreeAndNil(Hooked); FreeAndNil(Backgrounds); {$IFDEF UNITVERSIONING} UnregisterUnitVersion(HInstance); {$ENDIF UNITVERSIONING} end.