{-----------------------------------------------------------------------------
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: JvScrollMax.PAS, released on 2002-07-04.

The Initial Developers of the Original Code are: Andrei Prygounkov <a.prygounkov@gmx.de>
Copyright (c) 1999, 2002 Andrei Prygounkov
All Rights Reserved.

Contributor(s):

Last Modified: 2002-07-04

You may retrieve the latest version of this file at the Project JEDI's JVCL home page,
located at http://jvcl.sourceforge.net

component   : TJvScrollMax
description : scrollable panels

Known Issues:
  Some russian comments were translated to english; these comments are marked
  with [translated]
-----------------------------------------------------------------------------}

{$I JVCL.INC}

{ History:
  1.20:
    - first version;
  2.00:
    - new property ScrollbarVisible;
}

unit JvScrollMax;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Forms,
  ExtCtrls, Controls, Buttons,
  //xarka
  Themes,
  //
  JvButtons, JvComponent;

const
  CM_PARENTBEVELEDCHANGED = WM_USER + 1;
  CM_PARENTBUTTONFONTCHANGED = WM_USER + 2;
  CM_PARENTBUTTONVISIBLECHANGED = WM_USER + 3;

type
  TOnCanExpand = procedure(Sender: TObject; var CanExpand: Boolean) of object;
  TOnCanCollapse = procedure(Sender: TObject; var CanCollapse: Boolean) of object;

  TJvScrollMax = class;

  TJvScrollMaxBand = class(TCustomControl)
  private
    FData: Pointer;
    FExpandedHeight: Integer;
    FButton: TSpeedButton;
    FExpanded: Boolean;
    FOrder: Integer;
    FBeveled: Boolean;
    FBorderWidth: Integer;
    FParentBeveled: Boolean;
    FParentButtonFont: Boolean;
    FParentButtonVisible: Boolean;
    FOnResize: TNotifyEvent;
    FOnExpand: TNotifyEvent;
    FOnCollapse: TNotifyEvent;
    FOnCanCollapse: TOnCanCollapse;
    FOnCanExpand: TOnCanExpand;
    procedure ButtonClick(Sender: TObject);
    procedure SetExpanded(const Value: Boolean);
    procedure SetExpandedHeight(const Value: Integer);
    function GetOrder: Integer;
    procedure SetOrder(const Value: Integer);
    procedure SetParentBeveled(const Value: Boolean);
    procedure SetButtonFont(Value: TFont);
    function GetButtonFont: TFont;
    procedure SetBeveled(const Value: Boolean);
    procedure SetBorderWidth(const Value: Integer);
    function IsBeveledStored: Boolean;
    procedure SetParentButtonFont(const Value: Boolean);
    function IsButtonFontStored: Boolean;
    function GetButtonVisible: Boolean;
    procedure SetButtonVisible(const Value: Boolean);
    function IsButtonVisibleStored: Boolean;
    procedure SetParentButtonVisible(const Value: Boolean);
    procedure CMTextChanged(var Msg: TMessage); message CM_TEXTCHANGED;
    procedure WMSize(var Msg: TWMSize); message WM_SIZE;
    procedure CMParentBeveledChanged(var Msg: TMessage); message CM_PARENTBEVELEDCHANGED;
    procedure CMParentButtonFontChanged(var Msg: TMessage); message CM_PARENTBUTTONFONTCHANGED;
    procedure CMParentButtonVisibleChanged(var Msg: TMessage); message CM_PARENTBUTTONVISIBLECHANGED;
  protected
    procedure Loaded; override;
    procedure Paint; override;
    procedure SetParent(AParent: TWinControl); override;
    procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); override;
    procedure MouseMove(Shift: TShiftState; X, Y: Integer); override;
    procedure MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); override;
    procedure SetZOrder(TopMost: Boolean); override;
    function ScrollMax: TJvScrollMax;
    procedure UpdateSize(ATop: Integer);
    {$IFDEF COMPILER2}
    procedure RequestAlign;
    {$ENDIF COMPILER2}
    procedure AlignControls(AControl: TControl; var Rect: TRect); override;
    function CollapsedHeight: Integer;
    procedure ChangeScale(M, D: Integer); override;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    property Data: Pointer read FData write FData;
  published
    //xarka
    property Button : TSpeedButton read FButton;
    //
    property Expanded: Boolean read FExpanded write SetExpanded default True;
    property Caption;
    property ExpandedHeight: Integer read FExpandedHeight write SetExpandedHeight;
    property Order: Integer read GetOrder write SetOrder stored False;
    property ButtonVisible: Boolean read GetButtonVisible write SetButtonVisible stored IsButtonVisibleStored;
    property ButtonFont: TFont read GetButtonFont write SetButtonFont stored IsButtonFontStored;
    property Beveled: Boolean read FBeveled write SetBeveled default True;
    property BorderWidth: Integer read FBorderWidth write SetBorderWidth default 0;
    property ParentBeveled: Boolean read FParentBeveled write SetParentBeveled stored IsBeveledStored;
    property ParentButtonVisible: Boolean read FParentButtonVisible write SetParentButtonVisible default True;
    property ParentButtonFont: Boolean read FParentButtonFont write SetParentButtonFont default True;
    property OnResize: TNotifyEvent read FOnResize write FOnResize;
    property OnExpand: TNotifyEvent read FOnExpand write FOnExpand;
    property OnCollapse: TNotifyEvent read FOnCollapse write FOnCollapse;
    property OnCanExpand: TOnCanExpand read FOnCanExpand write FOnCanExpand;
    property OnCanCollapse: TOnCanCollapse read FOnCanCollapse write FOnCanCollapse;
    property Left stored False;
    property Top stored False;
    property Width stored False;
    property Height stored False;
    property Color;
    property Font;
    property ParentColor;
    property ParentFont;
    property ParentShowHint;
    property ShowHint;
    property Visible;
    property OnDblClick;
    property OnDragDrop;
    property OnDragOver;
    property OnEndDrag;
    property OnStartDrag;
    {$IFDEF COMPILER4_UP}
    property BiDiMode;
    property ParentBiDiMode;
    {$ENDIF COMPILER4_UP}
  end;

  TJvScrollMaxBands = class(TCustomControl)
  private
    FScrolling: Boolean;
    procedure CMFocusChanged(var Msg: TCMFocusChanged); message CM_FOCUSCHANGED;
  protected
    procedure AlignControls(AControl: TControl; var Rect: TRect); override;
    procedure ScrollControls(const DeltaY: Integer);
    procedure Paint; override;
  end;

  TJvScrollBar = class(TCustomPanel)
  private
    FMin: Integer;
    FMax: Integer;
    FPos: Integer;
    FPage: Integer;
    Scroll: TPanel;
    FDesignInteractive: Boolean;
    FInclusive: Boolean;
    FOnChange: TNotifyEvent;
    FOnScroll: TNotifyEvent;
    procedure SetParam(Index, Value: Integer);
    procedure SetInclusive(Value: Boolean);
  protected
    procedure CreateWnd; override;
    procedure SetTrackBar;
    procedure Loaded; override;
    procedure Resize; override;
  public
    constructor Create(AOwner: TComponent); override;
    procedure SetParams(const AMin, AMax, APage, APos: Integer);
    property Pos: Integer index 3 read FPos write SetParam;
    property DesignInteractive: Boolean read FDesignInteractive write FDesignInteractive;
    property Scroller: TPanel read Scroll;
  published
    property Color;
    property Align;
    property Min: Integer index 0 read FMin write SetParam;
    property Max: Integer index 1 read FMax write SetParam;
    property Page: Integer index 2 read FPage write SetParam;
    property Position: Integer index 3 read FPos write SetParam;
    property Inclusive: Boolean read FInclusive write SetInclusive;
    property OnChange: TNotifyEvent read FOnChange write FOnChange;
    property OnScroll: TNotifyEvent read FOnScroll write FOnScroll;
  end;

  TJvScrollMax = class(TJvCustomPanel)
  private
    FPnlEdit: TJvScrollMaxBands;
    FScrollBar: TJvScrollBar;
    FScrollPos: Integer;
    Yy: Integer;
    FButtonFont: TFont;
    FOnScroll: TNotifyEvent;
    FBeveled: Boolean;
    FButtonVisible: Boolean;
    FAutoHeight: Boolean;
    FExpandedHeight: Integer;
    FOneExpanded: Boolean;
    procedure Correct;
    procedure CorrectHeight;
    procedure BandMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
    procedure BandMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
    procedure BandMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
    procedure ScrollBarScroll(Sender: TObject);
    procedure SetButtonFont(Value: TFont);
    procedure ButtonFontChanged(Sender: TObject);
    function GetBand(Index: Integer): TJvScrollMaxBand;
    function GetBandCount: Integer;
    procedure SetScrollPos(const Value: Integer);
    procedure SetButtonVisible(const Value: Boolean);
    procedure SetBeveled(const Value: Boolean);
    procedure SetAutoHeight(const Value: Boolean);
    procedure SetExpandedHeight(const Value: Integer);
    function GetScrollBarWidth: Cardinal;
    procedure SetScrollBarWidth(const Value: Cardinal);
    function GetScrollBarVisible: Boolean;
    procedure SetScrollBarVisible(const Value: Boolean);
    procedure SetOneExpanded(const Value: Boolean);
  protected
    procedure Loaded; override;
    {$IFDEF COMPILER2}
    procedure GetChildren(Proc: TGetChildProc); override;
    {$ELSE}
    procedure GetChildren(Proc: TGetChildProc; Root: TComponent); override;
    {$ENDIF  COMPILER2}
    function GetChildParent: TComponent; override;
    procedure CreateParams(var Params: TCreateParams); override;
    procedure Resize; override;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    procedure ScrollInView(AControl: TControl);
    procedure MouseControls(AControls: array of TControl);
    procedure MouseClasses(AControlClasses: array of TControlClass);
    function AllCollapsed: Boolean;
    function AllExpanded: Boolean;
    procedure AddBand(Band: TJvScrollMaxBand);
    property BandCount: Integer read GetBandCount;
    property Bands[Index: Integer]: TJvScrollMaxBand read GetBand;
  published
    property ScrollPos: Integer read FScrollPos write SetScrollPos default 0;
    property BorderWidth default 3;
    property Beveled: Boolean read FBeveled write SetBeveled default True;
    property ButtonFont: TFont read FButtonFont write SetButtonFont;
    property ButtonVisible: Boolean read FButtonVisible write SetButtonVisible default True;
    property AutoHeight: Boolean read FAutoHeight write SetAutoHeight;
    property ExpandedHeight: Integer read FExpandedHeight write SetExpandedHeight default -1;
    property ScrollBarWidth: Cardinal read GetScrollBarWidth write SetScrollBarWidth default 7;
    property ScrollBarVisible: Boolean read GetScrollBarVisible write SetScrollBarVisible default True;
    property OneExpanded: Boolean read FOneExpanded write SetOneExpanded default False;
    property OnScroll: TNotifyEvent read FOnScroll write FOnScroll;
    property Align;
    property BevelInner;
    property BevelOuter;
    property BevelWidth;
    property BorderStyle;
    property Color;
    property Ctl3D;
    property DragCursor;
    property DragMode;
    property Enabled;
    property Font;
    property ParentColor;
    property ParentCtl3D;
    property ParentFont;
    property ParentShowHint;
    property ShowHint;
    property TabOrder;
    property TabStop;
    property Visible;
    property OnResize;
    property OnDblClick;
    property OnDragDrop;
    property OnDragOver;
    property OnEndDrag;
    property OnStartDrag;
    {$IFDEF COMPILER4_UP}
  public
    property DockManager;
  published
    property Anchors;
    //property AutoSize;
    property BiDiMode;
    property Constraints;
    property UseDockManager default True;
    property DockSite;
    property DragKind;
    property ParentBiDiMode;
    property OnCanResize;
    property OnConstrainedResize;
    property OnDockDrop;
    property OnDockOver;
    property OnEndDock;
    property OnGetSiteInfo;
    property OnStartDock;
    property OnUnDock;
    {$ENDIF COMPILER4_UP}
  end;

  EJvScrollMaxError = class(Exception);

var
  crRAHand: Integer;
  crRAHandMove: Integer;

implementation

uses
  JvDsgnIntf;

{ Cursors resources }
{$R *.RES}

function PanelBorder(Panel: TCustomPanel): Integer;
begin
  Result := TPanel(Panel).BorderWidth;
  if TPanel(Panel).BevelOuter <> bvNone then
    Inc(Result, TPanel(Panel).BevelWidth);
  if TPanel(Panel).BevelInner <> bvNone then
    Inc(Result, TPanel(Panel).BevelWidth);
end;

{ function DefineCursor was typed from
  book "Secrets of Delphi 2" by Ray Lischner }

function DefineCursor(Identifer: PChar): TCursor;
var
  Handle: HCURSOR;
begin
  Handle := LoadCursor(HInstance, Identifer);
  if Handle = 0 then
    raise EOutOfResources.Create('Cannot load cursor resource');
  for Result := 1 to High(TCursor) do
    if Screen.Cursors[Result] = Screen.Cursors[crDefault] then
    begin
      Screen.Cursors[Result] := Handle;
      Exit;
    end;
  raise EOutOfResources.Create('Too many user-defined cursors');
end;

//=== TJvScroller ============================================================

type
  TJvScroller = class(TPanel)
  private
    Yy: Integer;
    procedure CMDesignHitTest(var Msg: TCMDesignHitTest); message CM_DESIGNHITTEST;
  protected
    procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); override;
    procedure MouseMove(Shift: TShiftState; X, Y: Integer); override;
  end;

procedure TJvScroller.MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
  if Button = mbLeft then
    Yy := Y;
end;

procedure TJvScroller.MouseMove(Shift: TShiftState; X, Y: Integer);
var
  Sm, T, OldPos: Integer;
begin
  if Shift = [ssLeft] then
  begin
    Sm := Yy - Y;
    T := Top;
    if Sm <> 0 then
    begin
      with Parent as TJvScrollBar do
      begin
        OldPos := Pos;
        Pos := Pos - Round(Sm * (FMax - FMin + 1) / ClientHeight);
        if (Pos <> OldPos) and Assigned(FOnScroll) then
          FOnScroll(Parent);
      end;
    end;
    Yy := Y - Top + T;
  end;
end;

procedure TJvScroller.CMDesignHitTest(var Msg: TCMDesignHitTest);
begin
  with (Owner as TJvScrollBar) do
    Msg.Result := Integer(FDesignInteractive and (FPage <> FMax - FMin + 1));
end;

//=== TJvScrollBar ===========================================================

constructor TJvScrollBar.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  BevelOuter := bvLowered;
  Color := clAppWorkSpace;
  Caption := '';
  ControlStyle := ControlStyle - [csSetCaption, csAcceptsControls];
  Scroll := TJvScroller.Create(Self);
  Scroll.Parent := Self;
  Scroll.Caption := '';
  Scroll.ControlStyle := ControlStyle - [csSetCaption, csAcceptsControls];
  FMax := 100;
  FPage := 10;
  Width := 20;
  Height := 100;
end;

procedure TJvScrollBar.Loaded;
begin
  inherited Loaded;
  Resize;
end;

procedure TJvScrollBar.Resize;
begin
  inherited Resize;
  with Scroll do
  begin
    Top := BevelWidth;
    Left := BevelWidth;
    Width := Self.Width - 2 * BevelWidth;
  end;
  SetTrackBar;
end;

procedure TJvScrollBar.SetTrackBar;
var
  CH, H, T: Integer;
  L, FP, P, P1: Integer;
begin
  { Before change of the code necessarily make a copy! [translated] }
  if FMin > FMax then
    FMin := FMax;
  if FPage > FMax - FMin + 1 then
    FPage := FMax - FMin + 1;
  if FInclusive then
    P := FPage
  else
    P := 0;
  P1 := FPage - P;
  if FPos > FMax - P then
    FPos := FMax - P;
  if FPos < FMin then
    FPos := FMin;
  L := FMax - FMin + 1;
  CH := Height - 2 * BevelWidth;
  H := Trunc(CH * FPage / L) + 1;
  FP := Trunc((FPos - FMin) / L * (L - P1)) + 1;
  T := Round(CH * FP / L);
  if H < 7 then
    H := 7;
  if H > CH then
    H := CH;
  if T < BevelWidth then
    T := BevelWidth;
  if T + H > Height - BevelWidth then
    T := Height - BevelWidth - H;
  if FPos = FMax - P then
    T := Height - BevelWidth - H;

  with Scroll do
    SetBounds(Left, T, Width, H);
end;

procedure TJvScrollBar.SetParam(Index, Value: Integer);
begin
  case Index of
    0:
      FMin := Value;
    1:
      FMax := Value;
    2:
      FPage := Value;
    3:
      FPos := Value;
  end;
  SetParams(FMin, FMax, FPage, FPos);
end;

procedure TJvScrollBar.SetParams(const AMin, AMax, APage, APos: Integer);
begin
  FMin := AMin;
  FMax := AMax;
  FPage := APage;
  FPos := APos;
  if Assigned(FOnChange) then
    FOnChange(Self);
  SetTrackBar;
end;

procedure TJvScrollBar.SetInclusive(Value: Boolean);
begin
  FInclusive := Value;
  SetTrackBar;
end;

procedure TJvScrollBar.CreateWnd;
begin
  inherited CreateWnd;
  SetTrackBar;
end;

//=== TJvBandBtn =============================================================

type
  TJvBandBtn = class(TJvNoFrameButton)
  private
    {$IFDEF COMPILER3_UP}
    procedure CMDesignHitTest(var Msg: TCMDesignHitTest); message CM_DESIGNHITTEST;
    {$ENDIF COMPILER3_UP}
    procedure CMFontChanged(var Msg: TMessage); message CM_FONTCHANGED;
  end;

{$IFDEF COMPILER3_UP}
procedure TJvBandBtn.CMDesignHitTest(var Msg: TCMDesignHitTest);
begin
  Msg.Result := 1;
end;
{$ENDIF COMPILER3_UP}

procedure TJvBandBtn.CMFontChanged(var Msg: TMessage);
begin
  inherited;
  if Parent <> nil then
    with Parent as TJvScrollMaxBand do
    begin
      FParentButtonFont := False;
      Canvas.Font := Self.Font;
      FButton.Height := Canvas.TextHeight('W') + 4;
      Invalidate;
    end;
end;

//=== TJvScrollMaxBand =======================================================

constructor TJvScrollMaxBand.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  ControlStyle := ControlStyle + [csSetCaption, csAcceptsControls];
  Height := 50;
  FExpandedHeight := 50;
  ParentColor := True;
  FParentButtonFont := True;
  FParentButtonVisible := True;
  FParentBeveled := True;
  //xarka
  // FButton := TJvBandBtn.Create(Self);
  if ThemeServices.ThemesEnabled
   then FButton := TSpeedButton.Create(Self)
   else FButton := TJvBandBtn.Create(Self);
  //

  with FButton do
  begin
    SetDesigning(False);
    Parent := Self;
    Top := 2;
    Left := 4;
    Cursor := crArrow;
    OnClick := ButtonClick;
    Margin := 4;
    Spacing := -1;
    //xarka
    //NoBorder := False;
    if not ThemeServices.ThemesEnabled then
     TJvBandBtn(FButton).NoBorder:=false;
    //
    ParentColor := True;
    {o}
    {$IFDEF COMPILER4_UP}
    FButton.ParentBiDiMode := True;
    {$ENDIF COMPILER4_UP}
  end;
  Expanded := True;
end;

destructor TJvScrollMaxBand.Destroy;
begin
  inherited Destroy;
end;

procedure TJvScrollMaxBand.Loaded;
begin
  inherited Loaded;
  Perform(CM_PARENTBEVELEDCHANGED, 0, 0);
  Perform(CM_PARENTBUTTONVISIBLECHANGED, 0, 0);
  Perform(CM_PARENTBUTTONFONTCHANGED, 0, 0);
end;

procedure TJvScrollMaxBand.WMSize(var Msg: TWMSize);
begin
  if FExpanded then
    ExpandedHeight := Height;
  inherited;
  if Assigned(FOnResize) then
    FOnResize(Self);
  if Parent <> nil then
    ScrollMax.CorrectHeight;
end;

procedure TJvScrollMaxBand.CMTextChanged(var Msg: TMessage);
begin
  FButton.Caption := Caption;
end;

procedure TJvScrollMaxBand.SetExpanded(const Value: Boolean);
begin
  if FExpanded <> Value then
  begin
    FExpanded := Value;
    if FExpanded then
      FButton.Glyph.LoadFromResourceName(HInstance, 'RASCROLLMAXBTNMINUS')
    else
      FButton.Glyph.LoadFromResourceName(HInstance, 'RASCROLLMAXBTNPLUS');
    if FExpanded and Assigned(FOnExpand) then
      FOnExpand(Self);
    if not FExpanded and Assigned(FOnCollapse) then
      FOnCollapse(Self);
    RequestAlign;
    if Parent <> nil then
      ScrollMax.CorrectHeight;
   { if not (csLoading in ComponentState) and (ScrollMax <> nil) then
      DesignerModified(ScrollMax); }
  end;
end;

procedure TJvScrollMaxBand.SetExpandedHeight(const Value: Integer);
begin
  if FExpandedHeight <> Value then
  begin
    FExpandedHeight := Value;
    if FExpanded then
      Height := FExpandedHeight;
     // RequestAlign - called from SetHeight
  end;
end;

function TJvScrollMaxBand.GetOrder: Integer;
var
  I: Integer;
begin
  Result := FOrder;
  if Parent <> nil then
  begin
    for I := 0 to Parent.ControlCount - 1 do
      if Parent.Controls[I] = Self then
      begin
        Result := I;
        Break;
      end;
  end;
end;

procedure TJvScrollMaxBand.SetOrder(const Value: Integer);
begin
  if FOrder <> Value then
  begin
    if Parent <> nil then
      TJvScrollMaxBands(Parent).SetChildOrder(Self, Value);
    FOrder := GetOrder;
    RequestAlign;
  end;
end;

function TJvScrollMaxBand.GetButtonFont: TFont;
begin
  Result := FButton.Font;
end;

procedure TJvScrollMaxBand.SetButtonFont(Value: TFont);
begin
  FButton.Font := Value;
end;

procedure TJvScrollMaxBand.SetParentButtonFont(const Value: Boolean);
begin
  if FParentButtonFont <> Value then
  begin
    FParentButtonFont := Value;
    if Parent <> nil then
      Perform(CM_PARENTBUTTONFONTCHANGED, 0, 0);
  end;
end;

procedure TJvScrollMaxBand.CMParentButtonFontChanged(var Msg: TMessage);
begin
  if FParentButtonFont then
  begin
    if ScrollMax <> nil then
      SetButtonFont(ScrollMax.FButtonFont);
    FParentButtonFont := True;
  end;
end;

function TJvScrollMaxBand.IsButtonFontStored: Boolean;
begin
  Result := not ParentButtonFont;
end;

function TJvScrollMaxBand.GetButtonVisible: Boolean;
begin
  Result := FButton.Visible;
end;

procedure TJvScrollMaxBand.SetButtonVisible(const Value: Boolean);
begin
  if FButton.Visible <> Value then
  begin
    FParentButtonVisible := False;
    FButton.Visible := Value;
    UpdateSize(Top);
    Invalidate;
  end;
end;

function TJvScrollMaxBand.IsButtonVisibleStored: Boolean;
begin
  Result := not ParentButtonVisible;
end;

procedure TJvScrollMaxBand.SetParentButtonVisible(const Value: Boolean);
begin
  if FParentButtonVisible <> Value then
  begin
    FParentButtonVisible := Value;
    if Parent <> nil then
      Perform(CM_PARENTBUTTONVISIBLECHANGED, 0, 0);
  end;
end;

procedure TJvScrollMaxBand.CMParentButtonVisibleChanged(var Msg: TMessage);
begin
  if FParentButtonVisible then
  begin
    if ScrollMax <> nil then
      SetButtonVisible(ScrollMax.FButtonVisible);
    FParentButtonVisible := True;
  end;
end;

procedure TJvScrollMaxBand.SetBeveled(const Value: Boolean);
begin
  if FBeveled <> Value then
  begin
    FParentBeveled := False;
    FBeveled := Value;
    UpdateSize(Top);
    Invalidate;
  end;
end;

function TJvScrollMaxBand.IsBeveledStored: Boolean;
begin
  Result := not ParentBeveled;
end;

procedure TJvScrollMaxBand.SetParentBeveled(const Value: Boolean);
begin
  if FParentBeveled <> Value then
  begin
    FParentBeveled := Value;
    if Parent <> nil then
      Perform(CM_PARENTBEVELEDCHANGED, 0, 0);
  end;
end;

procedure TJvScrollMaxBand.CMParentBeveledChanged(var Msg: TMessage);
begin
  if FParentBeveled then
  begin
    if ScrollMax <> nil then
      SetBeveled(ScrollMax.FBeveled);
    FParentBeveled := True;
  end;
end;

procedure TJvScrollMaxBand.ButtonClick(Sender: TObject);
var
  E: Boolean;
begin
  E := True;
  if FExpanded then
  begin
    if Assigned(FOnCanCollapse) then
      FOnCanCollapse(Self, E);
  end
  else
  if Assigned(FOnCanExpand) then
    FOnCanExpand(Self, E);
  if E then
    Expanded := not FExpanded;
  DesignerModified(Self);
end;

procedure TJvScrollMaxBand.SetParent(AParent: TWinControl);
begin
  if not ((AParent is TJvScrollMaxBands) or (AParent = nil)) then
    raise EJvScrollMaxError.Create('TJvScrollMaxBand can be put only into TJvScrollMax component');
  inherited SetParent(AParent);
  if not (csLoading in ComponentState) then
  begin
    Perform(CM_PARENTBEVELEDCHANGED, 0, 0);
    Perform(CM_PARENTBUTTONVISIBLECHANGED, 0, 0);
    Perform(CM_PARENTBUTTONFONTCHANGED, 0, 0);
  end;
end;

procedure TJvScrollMaxBand.SetZOrder(TopMost: Boolean);
begin
  inherited SetZOrder(TopMost);
  RequestAlign;
end;

procedure TJvScrollMaxBand.MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
  inherited MouseDown(Button, Shift, X, Y);
  ScrollMax.BandMouseDown(Self, Button, Shift, X, Y);
end;

procedure TJvScrollMaxBand.MouseMove(Shift: TShiftState; X, Y: Integer);
begin
  inherited MouseMove(Shift, X, Y);
  ScrollMax.BandMouseMove(Self, Shift, X, Y);
end;

procedure TJvScrollMaxBand.MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
  inherited MouseUp(Button, Shift, X, Y);
  ScrollMax.BandMouseUp(Self, Button, Shift, X, Y);
end;

function TJvScrollMaxBand.ScrollMax: TJvScrollMax;
begin
  if (Parent <> nil) and ((Parent as TJvScrollMaxBands).Parent <> nil) then
    Result := (Parent as TJvScrollMaxBands).Parent as TJvScrollMax
  else
    Result := nil;
end;

function TJvScrollMaxBand.CollapsedHeight: Integer;
begin
  //xarka
  Result := FButton.BoundsRect.Bottom + FButton.Top + 1;
end;

procedure TJvScrollMaxBand.UpdateSize(ATop: Integer);
var
  W, H: Integer;
begin
  if FExpanded then
    H := FExpandedHeight
  else
    H := CollapsedHeight;
  if ScrollMax <> nil then
  begin
    W := Parent.Width;
    if ScrollMax.ScrollBarVisible then
      W := W - 3;
  end
  else
    W := Width;
  SetBounds(0, ATop, W, H);
  if FBeveled then
    FButton.Left := 16
  else
    FButton.Left := 4;
  FButton.Width := Width - FButton.Left * 2;
end;

{$IFDEF COMPILER2}
procedure TJvScrollMaxBand.RequestAlign;
begin
  if Parent <> nil then
    Left := Left - 1;
end;
{$ENDIF COMPILER2}

procedure TJvScrollMaxBand.Paint;
var
  R: TRect;
  //xarka
  Box: TThemedButton;
  Details: TThemedElementDetails;
  //
const
  Ex: array [Boolean] of Integer = (BF_TOP, BF_RECT);
begin
  inherited Paint;
  if Canvas.Handle <> 0 then
  begin
    if csDesigning in ComponentState then
      DrawDesignFrame(Canvas, ClientRect);
    if FBeveled then
    begin
      R.Left := 1;
      if ButtonVisible then
        R.Top := FButton.Top + FButton.Height div 2
      else
        R.Top := 1;
      R.Right := Width - R.Left;
      R.Bottom := Height - 1;

      //xarka
      if not ThemeServices.ThemesEnabled then
       begin
        DrawEdge(Canvas.Handle, R, EDGE_ETCHED, Ex[FExpanded]);
        if ButtonVisible then
        begin
          Canvas.Brush.Color := Color;
          Canvas.Brush.Style := bsSolid;
          Canvas.FillRect(Bounds(FButton.Left - 2, R.Top, FButton.Width + 4, 2));
        end;
       end
      else
       begin
        box:=tbPushButtonNormal;
        Details := ThemeServices.GetElementDetails(Box);
        ThemeServices.DrawElement(canvas.Handle, Details, r);
       end;
      //
    end;
  end;
end;

procedure TJvScrollMaxBand.AlignControls(AControl: TControl; var Rect: TRect);
var
  BevelSize: Integer;
begin
  BevelSize := FBorderWidth;
  if FBeveled then
    Inc(BevelSize, 3);
  InflateRect(Rect, -BevelSize, -BevelSize);
  if ButtonVisible then
  begin
    Inc(Rect.Top, FButton.Height);
    if FButton.Top > FBorderWidth then
      Inc(Rect.Top, FButton.Top);
  end;
  inherited AlignControls(AControl, Rect);
end;

procedure TJvScrollMaxBand.SetBorderWidth(const Value: Integer);
begin
  if FBorderWidth <> Value then
  begin
    FBorderWidth := Value;
    Realign;
  end;
end;

procedure TJvScrollMaxBand.ChangeScale(M, D: Integer);
begin
  inherited ChangeScale(M, D);
  ExpandedHeight := FExpandedHeight * M div D;
end;

//=== TJvScrollMaxBands ======================================================

procedure TJvScrollMaxBands.AlignControls(AControl: TControl; var Rect: TRect);

  procedure AdjustBottom;
  begin
    if (Controls[ControlCount - 1].BoundsRect.Bottom < Height) and
      (Controls[0].Top < 0) then
    begin
      if Height - (Controls[ControlCount - 1].BoundsRect.Bottom - Controls[0].Top) > 0 then
        ScrollControls(-Controls[0].Top)
      else
        ScrollControls(Height - Controls[ControlCount - 1].BoundsRect.Bottom);
    end;
  end;

  procedure AdjustBand;
  var
    Band: TJvScrollMaxBand;
  begin
    Band := AControl as TJvScrollMaxBand;
    if (Band <> nil) and Band.FExpanded and
      (Band.BoundsRect.Bottom > Height) and
      (Band.Top > 0) and
      not (csLoading in Band.ComponentState) then
    begin
      ScrollControls(Height - Band.BoundsRect.Bottom);
    end;
  end;

  procedure SetCursor;
  var
    I: Integer;
    Cursor: TCursor;
  begin
    if (Controls[ControlCount - 1].BoundsRect.Bottom > ClientHeight) or
      (Controls[0].Top < 0) then
      Cursor := crRAHand
    else
      Cursor := crDefault;
    for I := 0 to ControlCount - 1 do
      Controls[I].Cursor := Cursor;
  end;

var
  I: Integer;
  ScrollMax: TJvScrollMax;
  T: Integer;
  SMax, SPage, SPos: Integer;
begin
  if FScrolling then
    Exit;
  if (Parent <> nil) and (csLoading in Parent.ComponentState) then
    Exit;
  ScrollMax := Parent as TJvScrollMax;
  if (AControl <> nil) and
    (AControl as TJvScrollMaxBand).FExpanded and
    ScrollMax.FOneExpanded then
    for I := 0 to ControlCount - 1 do
      if not (Controls[I] is TJvScrollMaxBand) then
        raise EJvScrollMaxError.Create('TJvScrollMax can contain only TJvScrollMaxBand components')
      else
      if Controls[I] <> AControl then
        (Controls[I] as TJvScrollMaxBand).Expanded := False;
  SPos := ScrollMax.FScrollPos;
  if ControlCount > 0 then
  begin
    for I := 0 to ControlCount - 1 do
    begin
      if not (Controls[I] is TJvScrollMaxBand) then
        raise EJvScrollMaxError.Create('TJvScrollMax can contain only TJvScrollMaxBand components');
      if I > 0 then
        T := Controls[I - 1].BoundsRect.Bottom
      else
        T := -ScrollMax.FScrollPos;
      (Controls[I] as TJvScrollMaxBand).UpdateSize(T);
    end;
    AdjustBottom;
    AdjustBand;
    SMax := Controls[ControlCount - 1].BoundsRect.Bottom - Controls[0].Top;
    SPos := -Controls[0].Top;
    ScrollMax.FScrollPos := SPos;
    SetCursor;
  end
  else
    SMax := Height;
  SPage := Height;
  ScrollMax.FScrollBar.SetParams(0, SMax, SPage, SPos);
end;

procedure TJvScrollMaxBands.ScrollControls(const DeltaY: Integer);
begin
  FScrolling := True;
  try
    ScrollBy(0, DeltaY);
  finally
    FScrolling := False;
  end;
end;

procedure TJvScrollMaxBands.CMFocusChanged(var Msg: TCMFocusChanged);
begin
  inherited;
  if (Msg.Sender <> nil) and
    ContainsControl(Msg.Sender) and
    (Parent <> nil) then
    (Parent as TJvScrollMax).ScrollInView(Msg.Sender);
end;

procedure TJvScrollMaxBands.Paint;
var
  R: TRect;
begin
  inherited Paint;
  if (csDesigning in ComponentState) and
    (ControlCount = 0) and
    (Canvas.Handle <> 0) then
  begin
    R := ClientRect;
    Canvas.Font.Color := clAppWorkSpace;
    DrawText(Canvas.Handle, 'Right click and choose "Add band"',
      -1, R, DT_WORDBREAK {or DT_CENTER or DT_VCENTER});
  end;
end;

//=== TJvScrollMax ===========================================================

constructor TJvScrollMax.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  ControlStyle := ControlStyle - [csSetCaption, csAcceptsControls];
  Caption := '';
  Width := 250;
  Height := 150;
  BorderWidth := 3;
  FExpandedHeight := -1;
  FButtonFont := TFont.Create;
  FButtonFont.Name := 'Small Fonts';
  FButtonFont.Size := 7;
  FButtonFont.OnChange := ButtonFontChanged;
  FButtonVisible := True;
  FBeveled := True;
  ParentColor := True;
  FPnlEdit := TJvScrollMaxBands.Create(Self);
  with FPnlEdit do
  begin
    Align := alClient;
    Parent := Self;
    ControlStyle := ControlStyle + [csAcceptsControls];
    ParentColor := True;
  end;
  FScrollBar := TJvScrollBar.Create(Self);
  with FScrollBar do
  begin
    Inclusive := True;
    Parent := Self;
    Width := 7;
    Align := alRight;
    Max := FPnlEdit.Height;
    Page := Self.Height;
    OnScroll := ScrollBarScroll;
    ParentColor := True;
    Visible := True;
    {$IFDEF COMPILER3_UP}
    DesignInteractive := True;
    {$ENDIF COMPILER3_UP}
  end;
end;

destructor TJvScrollMax.Destroy;
begin
  FButtonFont.Free;
  inherited Destroy;
end;

procedure TJvScrollMax.CreateParams(var Params: TCreateParams);
begin
  inherited CreateParams(Params);
  with Params do
  begin
    Style := Style or WS_CLIPCHILDREN;
    ExStyle := ExStyle or WS_EX_CONTROLPARENT;
  end;
end;

procedure TJvScrollMax.Loaded;
begin
  inherited Loaded;
  Resize;
  FPnlEdit.Realign;
end;

procedure TJvScrollMax.SetButtonFont(Value: TFont);
begin
  FButtonFont.Assign(Value);
end;

procedure TJvScrollMax.SetButtonVisible(const Value: Boolean);
begin
  if FButtonVisible <> Value then
  begin
    FButtonVisible := Value;
    FPnlEdit.NotifyControls(CM_PARENTBUTTONVISIBLECHANGED);
  end;
end;

procedure TJvScrollMax.SetBeveled(const Value: Boolean);
begin
  if FBeveled <> Value then
  begin
    FBeveled := Value;
    FPnlEdit.NotifyControls(CM_PARENTBEVELEDCHANGED);
  end;
end;

procedure TJvScrollMax.ButtonFontChanged(Sender: TObject);
begin
  FPnlEdit.NotifyControls(CM_PARENTBUTTONFONTCHANGED);
end;

procedure TJvScrollMax.MouseControls(AControls: array of TControl);
var
  I: Integer;
begin
  for I := Low(AControls) to High(AControls) do
  begin
    TJvScrollMax(AControls[I]).OnMouseDown := BandMouseDown;
    TJvScrollMax(AControls[I]).OnMouseMove := BandMouseMove;
    TJvScrollMax(AControls[I]).OnMouseUp := BandMouseUp;
  end;
end;

procedure TJvScrollMax.MouseClasses(AControlClasses: array of TControlClass);
var
  I, iB, iC: Integer;
begin
  for I := Low(AControlClasses) to High(AControlClasses) do
    for iB := 0 to BandCount - 1 do
      for iC := 0 to Bands[iB].ControlCount - 1 do
        if Bands[iB].Controls[iC] is AControlClasses[I] then
        begin
          TJvScrollMax(Bands[iB].Controls[iC]).OnMouseDown := BandMouseDown;
          TJvScrollMax(Bands[iB].Controls[iC]).OnMouseMove := BandMouseMove;
          TJvScrollMax(Bands[iB].Controls[iC]).OnMouseUp := BandMouseUp;
        end;
end;

procedure TJvScrollMax.Correct;
var
  Sm: Integer;
  CH: Integer;
begin
  if BandCount > 0 then
  begin
    Sm := 0;
    CH := FPnlEdit.Height;
    if (Bands[BandCount - 1].BoundsRect.Bottom < CH) and (Bands[0].Top < 0) then
      Sm := (CH - Bands[BandCount - 1].BoundsRect.Bottom);
    if Bands[0].Top + Sm > 0 then
      Sm := -Bands[0].Top;
    if Sm <> 0 then
    begin
      FPnlEdit.ScrollControls(Sm);
      FScrollBar.Pos := -Bands[0].Top;
      FScrollPos := FScrollBar.Pos;
    end;
  end;
end;

procedure TJvScrollMax.BandMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
var
  CH: Integer;
begin
  if (Button = mbLeft) and (BandCount > 0) then
  begin
    Yy := (Sender as TControl).ClientToScreen(Point(0, Y)).Y;
    CH := FPnlEdit.Height;
    if (Bands[BandCount - 1].BoundsRect.Bottom > CH) or
      (Bands[0].Top < 0) then
      Screen.Cursor := crRAHandMove
    else
      Screen.Cursor := crDefault;
  end;
end;

procedure TJvScrollMax.BandMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
var
  Sm: Integer;
  CH: Integer;
begin
  if (ssLeft in Shift) and (BandCount > 0) then
  begin
    Y := (Sender as TControl).ClientToScreen(Point(0, Y)).Y;
    CH := FPnlEdit.Height;
    if not (Sender = FScrollBar.Scroller) then
      Sm := Y - Yy
    else
      Sm := Yy - Y;
    if Sm < 0 then {Up}
    begin
      if not (Bands[BandCount - 1].BoundsRect.Bottom > CH) then
        Sm := 0
      else
      if Bands[BandCount - 1].BoundsRect.Bottom + Sm < CH then
        Sm := CH - Bands[BandCount - 1].BoundsRect.Bottom;
    end
    else
    if Sm > 0 then {Down}
    begin
      if not (Bands[0].Top < 0) then
        Sm := 0
      else
      if Bands[0].Top + Sm > 0 then
        Sm := -Bands[0].Top;
    end;
    if Sm <> 0 then
    begin
      FPnlEdit.ScrollControls(Sm);
      FScrollBar.Pos := -Bands[0].Top;
      FScrollPos := FScrollBar.Pos;
    end;
    Yy := Y;
    Correct;
  end;
end;

procedure TJvScrollMax.BandMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
  Screen.Cursor := crDefault;
end;

function TJvScrollMax.GetBand(Index: Integer): TJvScrollMaxBand;
begin
  Result := TJvScrollMaxBand(FPnlEdit.Controls[Index]);
end;

function TJvScrollMax.GetBandCount: Integer;
begin
  Result := FPnlEdit.ControlCount;
end;

{$IFDEF COMPILER2}
procedure TJvScrollMax.GetChildren(Proc: TGetChildProc);
begin
  FPnlEdit.GetChildren(Proc);
end;
{$ELSE}
procedure TJvScrollMax.GetChildren(Proc: TGetChildProc; Root: TComponent);
begin
  FPnlEdit.GetChildren(Proc, Root);
end;
{$ENDIF COMPILER2}

function TJvScrollMax.GetChildParent: TComponent;
begin
  Result := FPnlEdit;
end;

procedure TJvScrollMax.SetScrollPos(const Value: Integer);
begin
  if FScrollPos <> Value then
  begin
    FScrollPos := Value;
    if not (csLoading in ComponentState) then
    begin
      if FScrollPos > FScrollBar.Max - FScrollBar.Page then
        FScrollPos := FScrollBar.Max - FScrollBar.Page;
      if FScrollPos < 0 then
        FScrollPos := 0;
      DesignerModified(Self);
      FPnlEdit.Realign;
    end;
  end;
end;

procedure TJvScrollMax.ScrollBarScroll(Sender: TObject);
begin
  ScrollPos := FScrollBar.Pos;
  if Assigned(FOnScroll) then
    FOnScroll(Self);
end;

procedure TJvScrollMax.ScrollInView(AControl: TControl);
var
  I: Integer;
  Band: TJvScrollMaxBand;
  Rect: TRect;
begin
  Band := nil;
  for I := 0 to FPnlEdit.ControlCount - 1 do
    if (FPnlEdit.Controls[I] as TJvScrollMaxBand).ContainsControl(AControl) then
    begin
      Band := FPnlEdit.Controls[I] as TJvScrollMaxBand;
      Break;
    end;
  if Band = nil then
    raise EJvScrollMaxError.CreateFmt('Control %s not a child of %s', [AControl.Name, Parent.Name]);
  Band.Expanded := True;
  Rect := AControl.ClientRect;
  Dec(Rect.Top, BevelWidth + BorderWidth + 4);
  Inc(Rect.Bottom, BevelWidth + BorderWidth + 4);
  Rect.TopLeft := ScreenToClient(AControl.ClientToScreen(Rect.TopLeft));
  Rect.BottomRight := ScreenToClient(AControl.ClientToScreen(Rect.BottomRight));
  if Rect.Top < 0 then
    ScrollPos := ScrollPos + Rect.Top
  else
  if Rect.Bottom > ClientHeight then
  begin
    if Rect.Bottom - Rect.Top > ClientHeight then
      Rect.Bottom := Rect.Top + ClientHeight;
    ScrollPos := ScrollPos + Rect.Bottom - ClientHeight;
  end;
end;

procedure TJvScrollMax.SetAutoHeight(const Value: Boolean);
begin
  if FAutoHeight <> Value then
  begin
    FAutoHeight := Value;
    if FAutoHeight then
      CorrectHeight;
  end;
end;

procedure TJvScrollMax.SetExpandedHeight(const Value: Integer);
begin
  if FExpandedHeight <> Value then
  begin
    FExpandedHeight := Value;
    if FAutoHeight then
      CorrectHeight;
  end;
end;

procedure TJvScrollMax.Resize;
begin
  inherited Resize;
  if FAutoHeight and (BandCount > 0) and
    not AllCollapsed and (FExpandedHeight > -1) then
    FExpandedHeight := Height;
  if FAutoHeight then
    CorrectHeight;
end;

procedure TJvScrollMax.CorrectHeight;
var
  I, H: Integer;
  Band: TJvScrollMaxBand;
begin
  if not FAutoHeight or (BandCount = 0) then
    Exit;
  if AllCollapsed then
  begin
    H := 0;
    for I := 0 to BandCount - 1 do
      Inc(H, Bands[I].Height);
    ClientHeight := H + 2 * PanelBorder(Self);
  end
  else
  if FExpandedHeight <> -1 then
    Height := FExpandedHeight
  else
  begin
    H := 0;
    Band := nil;
    for I := 0 to BandCount - 1 do
      if Bands[I].Height > H then
      begin
        Band := Bands[I];
        H := Band.Height;
      end;
    H := 0;
    for I := 0 to BandCount - 1 do
      if Bands[I] = Band then
        Inc(H, Bands[I].Height)
      else
        Inc(H, Bands[I].CollapsedHeight);
    ClientHeight := H + 2 * PanelBorder(Self);
  end;
end;

function TJvScrollMax.AllCollapsed: Boolean;
var
  I: Integer;
begin
  Result := False;
  for I := 0 to BandCount - 1 do
    if Bands[I].Expanded then
      Exit;
  Result := True;
end;

function TJvScrollMax.AllExpanded: Boolean;
var
  I: Integer;
begin
  Result := False;
  for I := 0 to BandCount - 1 do
    if not Bands[I].Expanded then
      Exit;
  Result := True;
end;

procedure TJvScrollMax.AddBand(Band: TJvScrollMaxBand);
begin
  Band.Parent := GetChildParent as TWinControl;
end;

function TJvScrollMax.GetScrollBarWidth: Cardinal;
begin
  Result := FScrollBar.Width;
end;

procedure TJvScrollMax.SetScrollBarWidth(const Value: Cardinal);
begin
  if Value >= 4 then
    FScrollBar.Width := Value;
end;

function TJvScrollMax.GetScrollBarVisible: Boolean;
begin
  Result := FScrollBar.Visible;
end;

procedure TJvScrollMax.SetScrollBarVisible(const Value: Boolean);
begin
  FScrollBar.Visible := Value;
  if csDesigning in ComponentState then
    if not Value then
      FScrollBar.Parent := nil
    else
      FScrollBar.Parent := Self;
end;

procedure TJvScrollMax.SetOneExpanded(const Value: Boolean);
begin
  if FOneExpanded <> Value then
  begin
    FOneExpanded := Value;
    { .. }
  end;
end;

initialization
  crRAHand := DefineCursor('RAHAND');
  crRAHandMove := DefineCursor('RAHANDMOVE');
end.

