{-----------------------------------------------------------------------------
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: JvSplitter.PAS, released on 2001-02-28.

The Initial Developer of the Original Code is Sébastien Buysse [sbuysse att buypin dott com]
Portions created by Sébastien Buysse are Copyright (C) 2001 Sébastien Buysse.
All Rights Reserved.

Contributor(s):
Michael Beck [mbeck att bigfoot dott com].
dejoy(dejoy att ynl dott gov dott cn)

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

Known Issues:
-----------------------------------------------------------------------------}
// $Id$

unit JvNetscapeSplitter;

{$I jvcl.inc}

interface

uses
  {$IFDEF UNITVERSIONING}
  JclUnitVersioning,
  {$ENDIF UNITVERSIONING}
  SysUtils, Classes,
  Windows, Messages, Graphics, Forms, ExtCtrls, Controls,
  JvExExtCtrls;

const
  MOVEMENT_TOLERANCE = 5; // See WMLButtonUp message handler.
  JvDefaultButtonHighlightColor = TColor($00FFCFCF); // RGB(207,207,255)

type
  TJvButtonWidthKind = (btwPixels, btwPercentage);
  TJvButtonStyle = (bsNetscape, bsWindows);
  TJvWindowsButton = (wbMin, wbMax, wbClose);
  TJvWindowsButtons = set of TJvWindowsButton;

  TJvCustomNetscapeSplitter = class(TJvExSplitter)
  private
    FBusy: Boolean;
    FShowButton: Boolean;
    FButtonWidthKind: TJvButtonWidthKind;
    FButtonWidth: Integer;
    FOnMaximize: TNotifyEvent;
    FOnMinimize: TNotifyEvent;
    FOnRestore: TNotifyEvent;
    FMaximized: Boolean;
    FMinimized: Boolean;
    // Internal use for "restoring" from "maximized" state
    FRestorePos: Integer;
    // For internal use to avoid calling GetButtonRect when not necessary
    FLastKnownButtonRect: TRect;
    // Internal use to avoid unecessary painting
    FIsHighlighted: Boolean;
    // Internal for detecting real clicks
    FGotMouseDown: Boolean;
    FButtonColor: TColor;
    FButtonHighlightColor: TColor;
    FArrowColor: TColor;
    FTextureColor1: TColor;
    FTextureColor2: TColor;
    FAutoHighlightColor: Boolean;
    FAllowDrag: Boolean;
    FButtonStyle: TJvButtonStyle;
    FWindowsButtons: TJvWindowsButtons;
    FOnClose   :TNotifyEvent;
    FOnButtonDblClick :TNotifyEvent;
    FOnLineDblClick   :TNotifyEvent;
    FButtonCursor: TCursor;
    procedure SetShowButton(const Value: Boolean);
    procedure SetButtonWidthKind(const Value: TJvButtonWidthKind);
    procedure SetButtonWidth(const Value: Integer);
    function GetButtonRect: TRect;
    procedure SetMaximized(const Value: Boolean);
    procedure SetMinimized(const Value: Boolean);
    function GetAlign: TAlign;
    procedure SetAlign(Value: TAlign);
    procedure SetArrowColor(const Value: TColor);
    procedure SetButtonColor(const Value: TColor);
    procedure SetButtonHighlightColor(const Value: TColor);
    procedure SetButtonStyle(const Value: TJvButtonStyle);
    procedure SetTextureColor1(const Value: TColor);
    procedure SetTextureColor2(const Value: TColor);
    procedure SetAutoHighlightColor(const Value: Boolean);
    procedure SetAllowDrag(const Value: Boolean);
    procedure SetWindowsButtons(const Value: TJvWindowsButtons);
    procedure SetButtonCursor(const Value: TCursor);
  protected
    // Internal use for moving splitter position with FindControl and
    // UpdateControlSize
    FControl: TControl;
    FDownPos: TPoint;
    procedure MouseEnter(Control: TControl); override;
    procedure MouseLeave(Control: TControl); override;
    procedure Paint; override;
    procedure WMLButtonDown(var Msg: TWMLButtonDown); message WM_LBUTTONDOWN;
    procedure WMLButtonUp(var Msg: TWMLButtonUp);     message WM_LBUTTONUP;
    procedure WMMouseMove(var Msg: TWMMouseMove);     message WM_MOUSEMOVE;
    procedure WMDblClick (var Msg:TWMLButtonDBLCLK);  message WM_LBUTTONDBLCLK;
    procedure LoadOtherProperties(Reader: TReader); dynamic;
    procedure StoreOtherProperties(Writer: TWriter); dynamic;
    procedure DefineProperties(Filer: TFiler); override;
    function DoCanResize(var NewSize: Integer): Boolean; override;
    procedure Loaded; override;
    procedure PaintButton(Highlight: Boolean); dynamic;
    function DrawArrow(ACanvas: TCanvas; AvailableRect: TRect; AOffset: Integer;
      ArrowSize: Integer; Color: TColor): Integer; dynamic;
    function WindowButtonHitTest(X, Y: Integer): TJvWindowsButton; dynamic;
    function ButtonHitTest(X, Y: Integer): Boolean; dynamic;
    procedure DoMaximize; dynamic;
    procedure DoMinimize; dynamic;
    procedure DoRestore; dynamic;
    procedure DoClose; dynamic;
    procedure FindControl; dynamic;
    procedure UpdateControlSize(NewSize: Integer); dynamic;
    function GrabBarColor: TColor;
    function VisibleWinButtons: Integer;
  public
    constructor Create(AOwner: TComponent); override;
    procedure SetBounds(ALeft, ATop, AWidth, AHeight: Integer); override;
    property ButtonRect: TRect read GetButtonRect;
    property RestorePos: Integer read FRestorePos write FRestorePos;
    property Maximized: Boolean read FMaximized write SetMaximized;
    property Minimized: Boolean read FMinimized  write SetMinimized;
    property AllowDrag: Boolean read FAllowDrag write SetAllowDrag default True;
    property ButtonCursor: TCursor read FButtonCursor write SetButtonCursor;
    property ButtonStyle: TJvButtonStyle read FButtonStyle write SetButtonStyle default bsNetscape;
    property WindowsButtons: TJvWindowsButtons read FWindowsButtons write SetWindowsButtons
      default [wbMin, wbMax, wbClose];
    property ButtonWidthKind: TJvButtonWidthKind read FButtonWidthKind write SetButtonWidthKind
      default btwPixels;
    property ButtonWidth: Integer read FButtonWidth write SetButtonWidth default 100;
    property ShowButton: Boolean read FShowButton write SetShowButton default True;
    property ButtonColor: TColor read FButtonColor write SetButtonColor default clBtnFace;
    property ArrowColor: TColor read FArrowColor write SetArrowColor default clNavy;
    property ButtonHighlightColor: TColor read FButtonHighlightColor write SetButtonHighlightColor
      default JvDefaultButtonHighlightColor;
    property AutoHighlightColor: Boolean read FAutoHighlightColor write SetAutoHighlightColor
      default False;
    property TextureColor1: TColor read FTextureColor1 write SetTextureColor1 default clWhite;
    property TextureColor2: TColor read FTextureColor2 write SetTextureColor2 default clNavy;
    property Align: TAlign read GetAlign write SetAlign; // Need to know when it changes to redraw arrows
    property Width default 10; // it looks best with 10
    property Beveled default False; // it looks best without the bevel
    property Enabled;
    property HintColor;
    property OnClose: TNotifyEvent read FOnClose write FOnClose;
    property OnMaximize: TNotifyEvent read FOnMaximize write FOnMaximize;
    property OnMinimize: TNotifyEvent read FOnMinimize write FOnMinimize;
    property OnRestore : TNotifyEvent read FOnRestore  write FOnRestore;
    property OnButtonDblClick: TNotifyEvent read FOnButtonDblClick write FOnButtonDblClick;
    property OnLineDblClick  : TNotifyEvent read FOnLineDblClick   write FOnLineDblClick;
    property OnParentColorChange;
  end;

  {$IFDEF RTL230_UP}
  [ComponentPlatformsAttribute(pidWin32 or pidWin64)]
  {$ENDIF RTL230_UP}
  TJvNetscapeSplitter = class(TJvCustomNetscapeSplitter)
  published
    property Maximized;
    property Minimized;
    property AllowDrag;
    property ButtonCursor;
    property ButtonStyle;
    property WindowsButtons;
    property ButtonWidthKind;
    property ButtonWidth;
    property ShowButton;
    property ButtonColor;
    property ArrowColor;
    property ButtonHighlightColor;
    property AutoHighlightColor;
    property TextureColor1;
    property TextureColor2;
    property Align;
    property Width;
    property Beveled;
    property Enabled;
    property ShowHint;
    property HintColor;
    property OnClose;
    property OnMaximize;
    property OnMinimize;
    property OnRestore;
    property OnMouseEnter;
    property OnMouseLeave;
    property OnParentColorChange;
    property OnButtonDblClick;
    property OnLineDblClick;
  end;

{$IFDEF UNITVERSIONING}
const
  UnitVersioning: TUnitVersionInfo = (
    RCSfile: '$URL$';
    Revision: '$Revision$';
    Date: '$Date$';
    LogPath: 'JVCL\run'
  );
{$ENDIF UNITVERSIONING}

implementation

uses
  JvThemes;

procedure SetRectEmpty(var R: TRect);
begin
  FillChar(R, SizeOf(TRect), #0);
end;

constructor TJvCustomNetscapeSplitter.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  IncludeThemeStyle(Self, [csParentBackground]);

  Beveled := False;
  FAllowDrag := True;
  FButtonStyle := bsNetscape;
  FWindowsButtons := [wbMin, wbMax, wbClose];
  FButtonWidthKind := btwPixels;
  FButtonWidth := 100;
  FShowButton := True;
  SetRectEmpty(FLastKnownButtonRect);
  FIsHighlighted := False;
  FGotMouseDown := False;
  FControl := nil;
  FDownPos := Point(0, 0);
  FMaximized := False;
  FMinimized := False;
  FRestorePos := -1;
  Width := 10;
  FButtonColor := clBtnFace;
  FArrowColor := clNavy;
  FButtonHighlightColor := JvDefaultButtonHighlightColor;
  FAutoHighlightColor := False;
  FTextureColor1 := clWhite;
  FTextureColor2 := clNavy;
end;



procedure TJvCustomNetscapeSplitter.MouseEnter(Control: TControl);
var
  Pos: TPoint;
begin
  if csDesigning in ComponentState then
    Exit;

  if not MouseOver then
  begin
    inherited MouseEnter(Control);

    //from dfs
    GetCursorPos(Pos); // CM_MOUSEENTER doesn't send mouse pos.
    Pos := Self.ScreenToClient(Pos);
    // The order is important here.  ButtonHitTest must be evaluated before
    // the ButtonStyle because it will change the cursor (over button or not).
    // If the order were reversed, the cursor would not get set for bsWindows
    // style since short-circuit Boolean eval would stop it from ever being
    // called in the first place.
    if ButtonHitTest(Pos.X, Pos.Y) and (ButtonStyle = bsNetscape) then
    begin
      if not FIsHighlighted then
        PaintButton(True)
    end
    else
    if FIsHighlighted then
      PaintButton(False);
  end;
end;

procedure TJvCustomNetscapeSplitter.MouseLeave(Control: TControl);
begin
  if MouseOver then
  begin
    inherited MouseLeave(Control);

    //from dfs
    if (ButtonStyle = bsNetscape) and FIsHighlighted then
      PaintButton(False);

    FGotMouseDown := False;
  end;
end;

procedure TJvCustomNetscapeSplitter.Paint;
{$IFDEF JVCLThemesEnabled}
var
  Bmp: TBitmap;
  DC: THandle;
{$ENDIF JVCLThemesEnabled}
begin
  if FBusy then
    Exit;
  FBusy := True;
  try
    // Exclude button rect from update region here for less flicker.
    {$IFDEF JVCLThemesEnabled}
    if StyleServices.Enabled then
    begin
      // DrawThemedBackground(Self, Canvas, ClientRect, Parent.Brush.Color);
      DC := Canvas.Handle;
      Bmp := TBitmap.Create;
      try
        Bmp.Width := ClientWidth;
        Bmp.Height := ClientHeight;
        Canvas.Handle := Bmp.Canvas.Handle;
        try
          inherited Paint;
        finally
          Canvas.Handle := DC;
        end;
        Bmp.Transparent := True;
        Bmp.TransparentColor := Color;
        Canvas.Draw(0, 0, Bmp);
      finally
        Bmp.Free;
      end;
    end
    else
      inherited Paint;
    {$ELSE}
    inherited Paint;
    {$ENDIF JVCLThemesEnabled}

    // Don't paint while being moved unless ResizeStyle = rsUpdate!!!
    // Make rect smaller if Beveled is True.
    PaintButton(FIsHighlighted);
  finally
    FBusy := False;
  end;
end;


//dfs

function TJvCustomNetscapeSplitter.ButtonHitTest(X, Y: Integer): Boolean;
begin
  // We use FLastKnownButtonRect here so that we don't have to recalculate the
  // button rect with GetButtonRect every time the mouse moved.  That would be
  // EXTREMELY inefficient.
  Result := PtInRect(FLastKnownButtonRect, Point(X, Y));
  if Align in [alLeft, alRight] then
  begin
    if (not AllowDrag) or ((Y >= FLastKnownButtonRect.Top) and
      (Y <= FLastKnownButtonRect.Bottom)) then
      Windows.SetCursor(Screen.Cursors[ButtonCursor])
    else
      Windows.SetCursor(Screen.Cursors[Cursor]);
  end
  else
  begin
    if (not AllowDrag) or ((X >= FLastKnownButtonRect.Left) and
      (X <= FLastKnownButtonRect.Right)) then
      Windows.SetCursor(Screen.Cursors[ButtonCursor])
    else
      Windows.SetCursor(Screen.Cursors[Cursor]);
  end;
end;

procedure TJvCustomNetscapeSplitter.DefineProperties(Filer: TFiler);
begin
  inherited DefineProperties(Filer);
  Filer.DefineProperty('RestorePos', LoadOtherProperties, StoreOtherProperties,
    Minimized or Maximized);
end;

function TJvCustomNetscapeSplitter.DoCanResize(var NewSize: Integer): Boolean;
begin
  Result := inherited DoCanResize(NewSize);
  // D4 version has a bug that causes it to not honor MinSize, which causes a
  // really nasty problem.
  if Result and (NewSize < MinSize) then
    NewSize := MinSize;
end;

procedure TJvCustomNetscapeSplitter.DoClose;
begin
  if Assigned(FOnClose) then
    FOnClose(Self);
end;

procedure TJvCustomNetscapeSplitter.DoMaximize;
begin
  if Assigned(FOnMaximize) then
    FOnMaximize(Self);
end;

procedure TJvCustomNetscapeSplitter.DoMinimize;
begin
  if Assigned(FOnMinimize) then
    FOnMinimize(Self);
end;

procedure TJvCustomNetscapeSplitter.DoRestore;
begin
  if Assigned(FOnRestore) then
    FOnRestore(Self);
end;

function TJvCustomNetscapeSplitter.DrawArrow(ACanvas: TCanvas; AvailableRect: TRect;
  AOffset, ArrowSize: Integer; Color: TColor): Integer;
var
  X, Y, Q, I, J: Integer;
  ArrowAlign: TAlign;
begin
  // STB Nitro drivers have a LineTo bug, so I've opted to use the slower
  // SetPixel method to draw the arrows.

  if not Odd(ArrowSize) then
    Dec(ArrowSize);
  if ArrowSize < 1 then
    ArrowSize := 1;

  if FMaximized then
  begin
    case Align of
      alLeft:
        ArrowAlign := alRight;
      alRight:
        ArrowAlign := alLeft;
      alTop:
        ArrowAlign := alBottom;
    else //alBottom
      ArrowAlign := alTop;
    end;
  end
  else
    ArrowAlign := Align;
  Q := ArrowSize * 2 - 1;
  Result := Q;
  ACanvas.Pen.Color := Color;
  case ArrowAlign of
    alLeft:
      begin
        X := AvailableRect.Left + ((AvailableRect.Right - AvailableRect.Left - ArrowSize) div 2) + 1;
        if AOffset < 0 then
          Y := AvailableRect.Bottom + AOffset - Q
        else
          Y := AvailableRect.Top + AOffset;
        for J := X + ArrowSize - 1 downto X do
        begin
          for I := Y to Y + Q - 1 do
            ACanvas.Pixels[J, I] := Color;
          Inc(Y);
          Dec(Q, 2);
        end;
      end;
    alRight:
      begin
        X := AvailableRect.Left + ((AvailableRect.Right - AvailableRect.Left - ArrowSize) div 2) + 1;
        if AOffset < 0 then
          Y := AvailableRect.Bottom + AOffset - Q
        else
          Y := AvailableRect.Top + AOffset;
        for J := X to X + ArrowSize - 1 do
        begin
          for I := Y to Y + Q - 1 do
            ACanvas.Pixels[J, I] := Color;
          Inc(Y);
          Dec(Q, 2);
        end;
      end;
    alTop:
      begin
        if AOffset < 0 then
          X := AvailableRect.Right + AOffset - Q
        else
          X := AvailableRect.Left + AOffset;
        Y := AvailableRect.Top + ((AvailableRect.Bottom - AvailableRect.Top - ArrowSize) div 2) + 1;
        for I := Y + ArrowSize - 1 downto Y do
        begin
          for J := X to X + Q - 1 do
            ACanvas.Pixels[J, I] := Color;
          Inc(X);
          Dec(Q, 2);
        end;
      end;
  else // alBottom
    if AOffset < 0 then
      X := AvailableRect.Right + AOffset - Q
    else
      X := AvailableRect.Left + AOffset;
    Y := AvailableRect.Top + ((AvailableRect.Bottom - AvailableRect.Top - ArrowSize) div 2) + 1;
    for I := Y to Y + ArrowSize - 1 do
    begin
      for J := X to X + Q - 1 do
        ACanvas.Pixels[J, I] := Color;
      Inc(X);
      Dec(Q, 2);
    end;
  end;
end;

procedure TJvCustomNetscapeSplitter.FindControl;
var
  P: TPoint;
  I: Integer;
  R: TRect;
begin
  if Parent = nil then
    Exit;
  FControl := nil;
  P := Point(Left, Top);
  case Align of
    alLeft:
      Dec(P.X);
    alRight:
      Inc(P.X, Width);
    alTop:
      Dec(P.Y);
    alBottom:
      Inc(P.Y, Height);
  else
    Exit;
  end;
  for I := 0 to Parent.ControlCount - 1 do
  begin
    FControl := Parent.Controls[I];
    if FControl.Visible and FControl.Enabled then
    begin
      R := FControl.BoundsRect;
      if (R.Right - R.Left) = 0 then
        Dec(R.Left);
      if (R.Bottom - R.Top) = 0 then
        Dec(R.Top);
      if PtInRect(R, P) then
        Exit;
    end;
  end;
  FControl := nil;
end;

function TJvCustomNetscapeSplitter.GetAlign: TAlign;
begin
  Result := inherited Align;
end;

function TJvCustomNetscapeSplitter.GetButtonRect: TRect;
var
  BW: Integer;
begin
  if ButtonStyle = bsWindows then
  begin
    if Align in [alLeft, alRight] then
      BW := (ClientRect.Right - ClientRect.Left) * VisibleWinButtons
    else
      BW := (ClientRect.Bottom - ClientRect.Top) * VisibleWinButtons;
    if BW < 1 then
      SetRectEmpty(Result)
    else
    begin
      if Align in [alLeft, alRight] then
        Result := Rect(0, 0, ClientRect.Right - ClientRect.Left,
          BW - VisibleWinButtons)
      else
        Result := Rect(ClientRect.Right - BW + VisibleWinButtons, 0,
          ClientRect.Right, ClientRect.Bottom - ClientRect.Top);
      InflateRect(Result, -1, -1);
    end;
  end
  else
  begin
    // Calc the rectangle the button goes in
    if ButtonWidthKind = btwPercentage then
    begin
      if Align in [alLeft, alRight] then
        BW := ClientRect.Bottom - ClientRect.Top
      else
        BW := ClientRect.Right - ClientRect.Left;
      BW := MulDiv(BW, FButtonWidth, 100);
    end
    else
      BW := FButtonWidth;
    if BW < 1 then
      SetRectEmpty(Result)
    else
    begin
      Result := ClientRect;
      if Align in [alLeft, alRight] then
      begin
        Result.Top := (ClientRect.Bottom - ClientRect.Top - BW) div 2;
        Result.Bottom := Result.Top + BW;
        InflateRect(Result, -1, 0);
      end
      else
      begin
        Result.Left := (ClientRect.Right - ClientRect.Left - BW) div 2;
        Result.Right := Result.Left + BW;
        InflateRect(Result, 0, -1);
      end;
    end;
  end;
  if not IsRectEmpty(Result) then
  begin
    if Result.Top < 1 then
      Result.Top := 1;
    if Result.Left < 1 then
      Result.Left := 1;
    if Result.Bottom >= ClientRect.Bottom then
      Result.Bottom := ClientRect.Bottom - 1;
    if Result.Right >= ClientRect.Right then
      Result.Right := ClientRect.Right - 1;
    // Make smaller if it's beveled
    if Beveled then
      if Align in [alLeft, alRight] then
        InflateRect(Result, -3, 0)
      else
        InflateRect(Result, 0, -3);
  end;
  FLastKnownButtonRect := Result;
end;

function TJvCustomNetscapeSplitter.GrabBarColor: TColor;
var
  BeginRGB: array [0..2] of Byte;
  RGBDifference: array [0..2] of Integer;
  R, G, B: Byte;
  BeginColor, EndColor: TColor;
  NumberOfColors: Integer;
begin
  //Need to figure out how many colors available at runtime
  NumberOfColors := 256;

  BeginColor := clActiveCaption;
  EndColor := clBtnFace;

  BeginRGB[0] := GetRValue(ColorToRGB(BeginColor));
  BeginRGB[1] := GetGValue(ColorToRGB(BeginColor));
  BeginRGB[2] := GetBValue(ColorToRGB(BeginColor));

  RGBDifference[0] := GetRValue(ColorToRGB(EndColor)) - BeginRGB[0];
  RGBDifference[1] := GetGValue(ColorToRGB(EndColor)) - BeginRGB[1];
  RGBDifference[2] := GetBValue(ColorToRGB(EndColor)) - BeginRGB[2];

  R := BeginRGB[0] + MulDiv(180, RGBDifference[0], NumberOfColors - 1);
  G := BeginRGB[1] + MulDiv(180, RGBDifference[1], NumberOfColors - 1);
  B := BeginRGB[2] + MulDiv(180, RGBDifference[2], NumberOfColors - 1);

  Result := RGB(R, G, B);
end;

procedure TJvCustomNetscapeSplitter.Loaded;
begin
  inherited Loaded;
  if FRestorePos = -1 then
  begin
    FindControl;
    if FControl <> nil then
      case Align of
        alLeft, alRight:
          FRestorePos := FControl.Width;
        alTop, alBottom:
          FRestorePos := FControl.Height;
      end;
  end;
end;

procedure TJvCustomNetscapeSplitter.LoadOtherProperties(Reader: TReader);
begin
  RestorePos := Reader.ReadInteger;
end;

procedure TJvCustomNetscapeSplitter.PaintButton(Highlight: Boolean);
const
  TEXTURE_SIZE = 3;
var
  BtnRect: TRect;
  CaptionBtnRect: TRect;
  BW: Integer;
  TextureBmp: TBitmap;
  X, Y: Integer;
  RW, RH: Integer;
  OffscreenBmp: TBitmap;
  WinButton: array [0..2] of TJvWindowsButton;
  B: TJvWindowsButton;
  BtnFlag: UINT;
begin
  if (not FShowButton) or (not Enabled) or (GetParentForm(Self) = nil) then
    Exit;

  if FAutoHighlightColor then
    FButtonHighlightColor := GrabBarColor;

  BtnRect := ButtonRect; // So we don't repeatedly call GetButtonRect
  if IsRectEmpty(BtnRect) then
    Exit; // nothing to draw

  OffscreenBmp := TBitmap.Create;
  try
    OffsetRect(BtnRect, -BtnRect.Left, -BtnRect.Top);
    OffscreenBmp.Width := BtnRect.Right;
    OffscreenBmp.Height := BtnRect.Bottom;

    if ButtonStyle = bsWindows then
    begin
      OffscreenBmp.Canvas.Brush.Color := Color;
      OffscreenBmp.Canvas.FillRect(BtnRect);
      if Align in [alLeft, alRight] then
        BW := BtnRect.Right
      else
        BW := BtnRect.Bottom;
      FillChar(WinButton, SizeOf(WinButton), 0);
      X := 0;
      if Align in [alLeft, alRight] then
      begin
        for B := High(TJvWindowsButton) downto Low(TJvWindowsButton) do
          if B in WindowsButtons then
          begin
            WinButton[X] := B;
            Inc(X);
          end;
      end
      else
      begin
        for B := Low(TJvWindowsButton) to High(TJvWindowsButton) do
          if B in WindowsButtons then
          begin
            WinButton[X] := B;
            Inc(X);
          end;
      end;
      for X := 0 to VisibleWinButtons - 1 do
      begin
        if Align in [alLeft, alRight] then
          CaptionBtnRect := Bounds(0, X * BW, BW, BW)
        else
          CaptionBtnRect := Bounds(X * BW, 0, BW, BW);
        BtnFlag := 0;
        case WinButton[X] of
          wbMin:
            if Minimized then
              BtnFlag := DFCS_CAPTIONRESTORE
            else
              BtnFlag := DFCS_CAPTIONMIN;
          wbMax:
            if Maximized then
              BtnFlag := DFCS_CAPTIONRESTORE
            else
              BtnFlag := DFCS_CAPTIONMAX;
          wbClose:
            BtnFlag := DFCS_CAPTIONCLOSE;
        end;
        DrawFrameControl(OffscreenBmp.Canvas.Handle,
          CaptionBtnRect, DFC_CAPTION, BtnFlag);
      end;
    end
    else
    begin
      // Draw basic button
      OffscreenBmp.Canvas.Brush.Color := clGray;
      OffscreenBmp.Canvas.FrameRect(BtnRect);
      InflateRect(BtnRect, -1, -1);

      OffscreenBmp.Canvas.Pen.Color := clWhite;
      // This is not going to work with the STB bug.  Have to find workaround.
      OffscreenBmp.Canvas.MoveTo(BtnRect.Left, BtnRect.Bottom - 1);
      OffscreenBmp.Canvas.LineTo(BtnRect.Left, BtnRect.Top);
      OffscreenBmp.Canvas.LineTo(BtnRect.Right, BtnRect.Top);

      Inc(BtnRect.Left);
      Inc(BtnRect.Top);

      if Highlight then
        OffscreenBmp.Canvas.Brush.Color := ButtonHighlightColor
      else
        OffscreenBmp.Canvas.Brush.Color := ButtonColor;
      OffscreenBmp.Canvas.FillRect(BtnRect);
      FIsHighlighted := Highlight;
      Dec(BtnRect.Right);
      Dec(BtnRect.Bottom);

      // Draw the insides of the button
      // Draw the arrows
      if Align in [alLeft, alRight] then
      begin
        InflateRect(BtnRect, 0, -4);
        BW := BtnRect.Right - BtnRect.Left;
        DrawArrow(OffscreenBmp.Canvas, BtnRect, 1, BW, ArrowColor);
        BW := DrawArrow(OffscreenBmp.Canvas, BtnRect, -1, BW, ArrowColor);
        InflateRect(BtnRect, 0, -(BW + 4));
      end
      else
      begin
        InflateRect(BtnRect, -4, 0);
        BW := BtnRect.Bottom - BtnRect.Top;
        DrawArrow(OffscreenBmp.Canvas, BtnRect, 1, BW, ArrowColor);
        BW := DrawArrow(OffscreenBmp.Canvas, BtnRect, -1, BW, ArrowColor);
        InflateRect(BtnRect, -(BW + 4), 0);
      end;

      // Draw the texture
      // Note: This is so complex because I'm trying to make as much like the
      //       Netscape splitter as possible.  They use a 3x3 texture pattern, and
      //       that's harder to tile.  If the had used an 8x8 (or smaller
      //       divisibly, i.e. 2x2 or 4x4), I could have used Brush.Bitmap and
      //       FillRect and they whole thing would have been about half the size,
      //       twice as fast, and 1/10th as complex.
      RW := BtnRect.Right - BtnRect.Left;
      RH := BtnRect.Bottom - BtnRect.Top;
      if (RW >= TEXTURE_SIZE) and (RH >= TEXTURE_SIZE) then
      begin
        TextureBmp := TBitmap.Create;
        try
          with TextureBmp do
          begin
            Width := RW;
            Height := RH;
            // Draw first square
            Canvas.Brush.Color := OffscreenBmp.Canvas.Brush.Color;
            Canvas.FillRect(Rect(0, 0, RW + 1, RH + 1));
            Canvas.Pixels[1, 1] := TextureColor1;
            Canvas.Pixels[2, 2] := TextureColor2;

            // Tile first square all the way across
            for X := 1 to ((RW div TEXTURE_SIZE) + ord(RW mod TEXTURE_SIZE > 0)) do
              Canvas.CopyRect(Bounds(X * TEXTURE_SIZE, 0, TEXTURE_SIZE,
                TEXTURE_SIZE), Canvas, Rect(0, 0, TEXTURE_SIZE, TEXTURE_SIZE));

            // Tile first row all the way down
            for Y := 1 to ((RH div TEXTURE_SIZE) + ord(RH mod TEXTURE_SIZE > 0)) do
              Canvas.CopyRect(Bounds(0, Y * TEXTURE_SIZE, RW, TEXTURE_SIZE),
                Canvas, Rect(0, 0, RW, TEXTURE_SIZE));

            // Above could be better if it reversed process when splitter was
            // taller than it was wider.  Optimized only for horizontal right now.
          end;
          // Copy texture bitmap to the screen.
          OffscreenBmp.Canvas.CopyRect(BtnRect, TextureBmp.Canvas,
            Rect(0, 0, RW, RH));
        finally
          TextureBmp.Free;
        end;
      end;
    end;
(**)
    Canvas.CopyRect(ButtonRect, OffscreenBmp.Canvas, Rect(0, 0,
      OffscreenBmp.Width, OffscreenBmp.Height));
  finally
    OffscreenBmp.Free;
  end;
end;

procedure TJvCustomNetscapeSplitter.SetAlign(Value: TAlign);
begin
  if Align <> Value then
  begin
    inherited Align := Value;
    Invalidate; // Direction changing, redraw arrows.
  end;
end;

procedure TJvCustomNetscapeSplitter.SetAllowDrag(const Value: Boolean);
var
  Pt: TPoint;
begin
  if FAllowDrag <> Value then
  begin
    FAllowDrag := Value;
    // Have to reset cursor in case it's on the splitter at the moment
    GetCursorPos(Pt);
    Pt := ScreenToClient(Pt);
    ButtonHitTest(Pt.X, Pt.Y);
  end;
end;

procedure TJvCustomNetscapeSplitter.SetArrowColor(const Value: TColor);
begin
  if FArrowColor <> Value then
  begin
    FArrowColor := Value;
    if (ButtonStyle = bsNetscape) and ShowButton then
      Invalidate;
  end;
end;

procedure TJvCustomNetscapeSplitter.SetAutoHighlightColor(const Value: Boolean);
begin
  if FAutoHighlightColor <> Value then
  begin
    FAutoHighlightColor := Value;
    if FAutoHighlightColor then
      FButtonHighlightColor := GrabBarColor
    else
      FButtonHighlightColor := JvDefaultButtonHighlightColor;
    if (ButtonStyle = bsNetscape) and ShowButton then
      Invalidate;
  end;
end;

procedure TJvCustomNetscapeSplitter.SetButtonColor(const Value: TColor);
begin
  if FButtonColor <> Value then
  begin
    FButtonColor := Value;
    if (ButtonStyle = bsNetscape) and ShowButton then
      Invalidate;
  end;
end;

procedure TJvCustomNetscapeSplitter.SetButtonCursor(const Value: TCursor);
begin
  FButtonCursor := Value;
end;

procedure TJvCustomNetscapeSplitter.SetButtonHighlightColor(const Value: TColor);
begin
  if FButtonHighlightColor <> Value then
  begin
    FButtonHighlightColor := Value;
    if (ButtonStyle = bsNetscape) and ShowButton then
      Invalidate;
  end;
end;

procedure TJvCustomNetscapeSplitter.SetButtonStyle(const Value: TJvButtonStyle);
begin
  FButtonStyle := Value;
  if ShowButton then
    Invalidate;
end;

procedure TJvCustomNetscapeSplitter.SetButtonWidth(const Value: Integer);
begin
  if Value <> FButtonWidth then
  begin
    FButtonWidth := Value;
    if (ButtonWidthKind = btwPercentage) and (FButtonWidth > 100) then
      FButtonWidth := 100;
    if FButtonWidth < 0 then
      FButtonWidth := 0;
    if (ButtonStyle = bsNetscape) and ShowButton then
      Invalidate;
  end;
end;

procedure TJvCustomNetscapeSplitter.SetButtonWidthKind(const Value: TJvButtonWidthKind);
begin
  if Value <> FButtonWidthKind then
  begin
    FButtonWidthKind := Value;
    if (FButtonWidthKind = btwPercentage) and (ButtonWidth > 100) then
      FButtonWidth := 100;
    if (ButtonStyle = bsNetscape) and ShowButton then
      Invalidate;
  end;
end;

procedure TJvCustomNetscapeSplitter.SetMaximized(const Value: Boolean);
begin
  if Value <> FMaximized then
  begin
    if csLoading in ComponentState then
    begin
      FMaximized := Value;
      Exit;
    end;

    FindControl;
    if FControl = nil then
      Exit;

    if Value then
    begin
      if FMinimized then
        FMinimized := False
      else
      begin
        case Align of
          alLeft, alRight:
            FRestorePos := FControl.Width;
          alTop, alBottom:
            FRestorePos := FControl.Height;
        else
          Exit;
        end;
      end;
      if ButtonStyle = bsNetscape then
        UpdateControlSize(-3000)
      else
        case Align of
          alLeft, alBottom:
            UpdateControlSize(3000);
          alRight, alTop:
            UpdateControlSize(-3000);
        else
          Exit;
        end;
      FMaximized := Value;
      DoMaximize;
    end
    else
    begin
      UpdateControlSize(FRestorePos);
      FMaximized := Value;
      DoRestore;
    end;
  end;
end;

procedure TJvCustomNetscapeSplitter.SetMinimized(const Value: Boolean);
begin
  if Value <> FMinimized then
  begin
    if csLoading in ComponentState then
    begin
      FMinimized := Value;
      Exit;
    end;

    FindControl;
    if FControl = nil then
      Exit;

    if Value then
    begin
      if FMaximized then
        FMaximized := False
      else
      begin
        case Align of
          alLeft, alRight:
            FRestorePos := FControl.Width;
          alTop, alBottom:
            FRestorePos := FControl.Height;
        else
          Exit;
        end;
      end;
      FMinimized := Value;
      // Just use something insanely large to get it to move to the other extreme
      case Align of
        alLeft, alBottom:
          UpdateControlSize(-3000);
        alRight, alTop:
          UpdateControlSize(3000);
      else
        Exit;
      end;
      DoMinimize;
    end
    else
    begin
      FMinimized := Value;
      UpdateControlSize(FRestorePos);
      DoRestore;
    end;
  end;
end;

procedure TJvCustomNetscapeSplitter.SetShowButton(const Value: Boolean);
begin
  if Value <> FShowButton then
  begin
    FShowButton := Value;
    SetRectEmpty(FLastKnownButtonRect);
    Invalidate;
  end;
end;

procedure TJvCustomNetscapeSplitter.SetTextureColor1(const Value: TColor);
begin
  if FTextureColor1 <> Value then
  begin
    FTextureColor1 := Value;
    if (ButtonStyle = bsNetscape) and ShowButton then
      Invalidate;
  end;
end;

procedure TJvCustomNetscapeSplitter.SetTextureColor2(const Value: TColor);
begin
  if FTextureColor2 <> Value then
  begin
    FTextureColor2 := Value;
    if (ButtonStyle = bsNetscape) and ShowButton then
      Invalidate;
  end;
end;

procedure TJvCustomNetscapeSplitter.SetWindowsButtons(const Value: TJvWindowsButtons);
begin
  FWindowsButtons := Value;
  if (ButtonStyle = bsWindows) and ShowButton then
    Invalidate;
end;

procedure TJvCustomNetscapeSplitter.StoreOtherProperties(Writer: TWriter);
begin
  Writer.WriteInteger(RestorePos);
end;

procedure TJvCustomNetscapeSplitter.UpdateControlSize(NewSize: Integer);

  procedure MoveViaMouse(FromPos, ToPos: Integer; Horizontal: Boolean);
  begin
    if Horizontal then
    begin
      MouseDown(mbLeft, [ssLeft], FromPos, 0);
      MouseMove([ssLeft], ToPos, 0);
      MouseUp(mbLeft, [ssLeft], ToPos, 0);
    end
    else
    begin
      MouseDown(mbLeft, [ssLeft], 0, FromPos);
      MouseMove([ssLeft], 0, ToPos);
      MouseUp(mbLeft, [ssLeft], 0, ToPos);
    end;
  end;

begin
  if FControl <> nil then
  begin
    { You'd think that using FControl directly would be the way to change it's
      position (and thus the splitter's position), wouldn't you?  But, TSplitter
      has this nutty idea that the only way a control's size will change is if
      the mouse moves the splitter.  If you size the control manually, the
      splitter has an internal variable (FOldSize) that will not get updated.
      Because of this, if you try to then move the newly positioned splitter
      back to the old position, it won't go there (NewSize <> OldSize must be
      True).  Now, what are the odds that the user will move the splitter back
      to the exact same pixel it used to be on?  Normally, extremely low.  But,
      if the splitter has been restored from it's minimized position, it then
      becomes quite likely:  i.e. they drag it back all the way to the min
      position.  What a pain. }
    case Align of
      alLeft:
        MoveViaMouse(Left, FControl.Left + NewSize, True);
              // alLeft: FControl.Width := NewSize;
      alTop:
        MoveViaMouse(Top, FControl.Top + NewSize, False);
             // FControl.Height := NewSize;
      alRight:
        MoveViaMouse(Left, (FControl.Left + FControl.Width - Width) - NewSize, True);
        {begin
          Parent.DisableAlign;
          try
            FControl.Left := FControl.Left + (FControl.Width - NewSize);
            FControl.Width := NewSize;
          finally
            Parent.EnableAlign;
          end;
        end;}
      alBottom:
        MoveViaMouse(Top, (FControl.Top + FControl.Height - Height) - NewSize, False);
        {begin
          Parent.DisableAlign;
          try
            FControl.Top := FControl.Top + (FControl.Height - NewSize);
            FControl.Height := NewSize;
          finally
            Parent.EnableAlign;
          end;
        end;}
    end;
    Update;
  end;
end;

function TJvCustomNetscapeSplitter.VisibleWinButtons: Integer;
var
  X: TJvWindowsButton;
begin
  Result := 0;
  for X := Low(TJvWindowsButton) to High(TJvWindowsButton) do
    if X in WindowsButtons then
      Inc(Result);
end;

function TJvCustomNetscapeSplitter.WindowButtonHitTest(X, Y: Integer): TJvWindowsButton;
var
  BtnRect: TRect;
  I: Integer;
  B: TJvWindowsButton;
  WinButton: array [0..2] of TJvWindowsButton;
  BW: Integer;
  BRs: array [0..2] of TRect;
begin
  Result := wbMin;
  // Figure out which one was hit.  This function assumes ButtonHitTest has
  // been called and returned True.
  BtnRect := ButtonRect; // So we don't repeatedly call GetButtonRect
  I := 0;
  if Align in [alLeft, alRight] then
  begin
    for B := High(TJvWindowsButton) downto Low(TJvWindowsButton) do
      if B in WindowsButtons then
      begin
        WinButton[I] := B;
        Inc(I);
      end;
  end
  else
    for B := Low(TJvWindowsButton) to High(TJvWindowsButton) do
      if B in WindowsButtons then
      begin
        WinButton[I] := B;
        Inc(I);
      end;

  if Align in [alLeft, alRight] then
    BW := BtnRect.Right - BtnRect.Left
  else
    BW := BtnRect.Bottom - BtnRect.Top;
  FillChar(BRs, SizeOf(BRs), 0);
  for I := 0 to VisibleWinButtons - 1 do
    if ((Align in [alLeft, alRight]) and PtInRect(Bounds(BtnRect.Left,
      BtnRect.Top + (BW * I), BW, BW), Point(X, Y))) or ((Align in [alTop,
      alBottom]) and PtInRect(Bounds(BtnRect.Left + (BW * I), BtnRect.Top, BW,
        BW), Point(X, Y))) then
    begin
      Result := WinButton[I];
      break;
    end;
end;

procedure TJvCustomNetscapeSplitter.SetBounds(ALeft, ATop, AWidth, AHeight: Integer);
begin
  inherited SetBounds(ALeft, ATop, AWidth, AHeight);
  if FRestorePos < 0 then
  begin
    FindControl;
    if FControl <> nil then
      case Align of
        alLeft, alRight:
          FRestorePos := FControl.Width;
        alTop, alBottom:
          FRestorePos := FControl.Height;
      end;
  end;
end;

procedure TJvCustomNetscapeSplitter.WMLButtonDown(var Msg: TWMLButtonDown);
begin
  if Enabled then
  begin
    FGotMouseDown := ButtonHitTest(Msg.XPos, Msg.YPos);
    if FGotMouseDown then
    begin
      FindControl;
      FDownPos := ClientToScreen(Point(Msg.XPos, Msg.YPos));
    end;
  end;
  if AllowDrag then
    inherited // Let TSplitter have it.
  else
    // Bypass TSplitter and just let normal handling occur. Prevents drag painting.
    DefaultHandler(Msg);
end;

procedure TJvCustomNetscapeSplitter.WMLButtonUp(var Msg: TWMLButtonUp);
var
  CurPos: TPoint;
  OldMax: Boolean;
begin
  inherited;

  if FGotMouseDown then
  begin
    if ButtonHitTest(Msg.XPos, Msg.YPos) then
    begin
      CurPos := ClientToScreen(Point(Msg.XPos, Msg.YPos));
      // More than a little movement is not a click, but a regular resize.
      if ((Align in [alLeft, alRight]) and
        (Abs(FDownPos.X - CurPos.X) <= MOVEMENT_TOLERANCE)) or
        ((Align in [alTop, alBottom]) and
        (Abs(FDownPos.Y - CurPos.Y) <= MOVEMENT_TOLERANCE)) then
      begin
        StopSizing;
        if ButtonStyle = bsNetscape then
          Maximized := not Maximized
        else
          case WindowButtonHitTest(Msg.XPos, Msg.YPos) of
            wbMin:
              Minimized := not Minimized;
            wbMax:
              Maximized := not Maximized;
            wbClose:
              DoClose;
          end;
      end;
    end;
    FGotMouseDown := False;
  end
  else
    if AllowDrag then
  begin
    FindControl;
    if FControl = nil then
      Exit;

    OldMax := FMaximized;
    case Align of
      alLeft, alRight:
        FMaximized := FControl.Width <= MinSize;
      alTop, alBottom:
        FMaximized := FControl.Height <= MinSize;
    end;
    if FMaximized then
    begin
      UpdateControlSize(MinSize);
      if not OldMax then
        DoMaximize;
    end
    else
    begin
      case Align of
        alLeft, alRight:
          FRestorePos := FControl.Width;
        alTop, alBottom:
          FRestorePos := FControl.Height;
      end;
      if OldMax then
        DoRestore;
    end;
  end;
  Invalidate;
end;

procedure TJvCustomNetscapeSplitter.WMMouseMove(var Msg: TWMMouseMove);
begin
  if AllowDrag then
  begin
    inherited;
  end
  else
  begin
    DefaultHandler(Msg); // Bypass TSplitter and just let normal handling occur.
  end;

  // Mantis 3718: The button is always highlighted whatever value AllowDrag is.

  // The order is important here.  ButtonHitTest must be evaluated before
  // the ButtonStyle because it will change the cursor (over button or not).
  // If the order were reversed, the cursor would not get set for bsWindows
  // style since short-circuit Boolean eval would stop it from ever being
  // called in the first place.
  if ButtonHitTest(Msg.XPos, Msg.YPos) and (ButtonStyle = bsNetscape) then
  begin
    if not FIsHighlighted then
      PaintButton(True)
  end
  else
  if FIsHighlighted then
    PaintButton(False);
end;

procedure TJvCustomNetscapeSplitter.WMDblClick(var Msg: TWMLButtonDBLCLK);
begin
  if ButtonHitTest(Msg.XPos, Msg.YPos) and (ButtonStyle = bsNetscape) then
  begin  //Double click in button
    if Assigned (FOnButtonDblClick) then
      FOnButtonDblClick(Self)
    else
      inherited;
  end
  else
  begin  //Double on body splitter line
    if Assigned (FOnLineDblClick) then
      FOnLineDblClick(Self)
    else
      inherited;
  end;
end;

{$IFDEF UNITVERSIONING}
initialization
  RegisterUnitVersion(HInstance, UnitVersioning);

finalization
  UnregisterUnitVersion(HInstance);
{$ENDIF UNITVERSIONING}

end.
