{==============================================================================

  Custom RangeBar
  Copyright (C) 2000-2001 by Evgeny Kryukov
  All rights reserved

  All contents of this file and all other files included in this archive
  are Copyright (C) 2001 Evgeny Kryukov. Use and/or distribution of
  them requires acceptance of the License Agreement.

  See License.txt for licence information

===============================================================================}

unit KsRangeBars;

{$I KsCommonVer.Inc}
{$T-,W-,X+,P+}

interface

uses
  Classes, Controls, Windows, Graphics, Messages, KsBitmap, KsUtils, KsControls,
  KsTrackBars;

const
  CStdRngThumbRatio = 2.2;

type

{ TKsCustomRangeBar is a advanced Windows track bar control with two sliders for select range. }
  TKsCustomRangeBar = class(TKsCustomControl)
  private
    FHighSliding, FLowSliding: boolean;
    FOrientation: TTrackOrientation;
    FMin: integer;
    FMax: integer;
    FShowTicks: boolean;
    FOnLowChange: TNotifyEvent;
    FOnHighChange: TNotifyEvent;
    FThumbWidth: word;
    FTickMarks: TTickMark;
    FMouseOnLowThumb: boolean;
    FMouseOnHighThumb: boolean;
    FLineSize: integer;
    FPageSize: integer;
    FFrequency: integer;
    FHighThumbVisible: boolean;
    FLowThumbVisible: boolean;
    FHighlightInterval: boolean;
    FHighPosition: integer;
    FLowPosition: integer;
    procedure SetMax(const Value: integer);
    procedure SetMin(const Value: integer);
    procedure SetShowTicks(const Value: boolean);
    procedure SetOrientation(const Value: TTrackOrientation);
    procedure SetThumbWidth(const Value: word);
    procedure SetTickMarks(const Value: TTickMark);

    procedure SetLineSize(const Value: Integer);
    procedure SetPageSize(const Value: Integer);
    procedure SetFrequency(const Value: Integer);
    procedure SetLowThumbVisible(const Value: Boolean);
    procedure SetHighThumbVisible(const Value: Boolean);
    procedure SetHighlightInterval(const Value: boolean);
    procedure SetHighPosition(const Value: integer);
    procedure SetLowPosition(const Value: integer);
  protected
    { Messages }
    procedure CMEnabledChanged(var Msg: TMessage); message cm_EnabledChanged;
    procedure WMGetDlgCode(var Msg: TWMGetDlgCode); message wm_GetDlgCode;
    { RangeBar }
    procedure LowChange; dynamic;
    procedure HighChange; dynamic;
    function PositionCoordinate(APos: integer): word;
    function CoordinatePosition(ACoord: word): integer;
    procedure DrawTrack(BegCoord, EndCoord, LowPosCoord, HighPosCoord: integer); virtual;
    procedure DrawLowThumb(APosCoord: integer); virtual;
    procedure DrawHighThumb(APosCoord: integer); virtual;
    procedure DrawTick(Value, Coord: integer; TopLeft: boolean); virtual;
    procedure DrawTicks; virtual;
    function GetSideMargin: integer; virtual;
    function GetTopBottomMargin: integer; virtual;
    function GetThumbWidth: integer; virtual;
    function PtInLowThumb(x, y: integer): boolean; virtual;
    function PtInHighThumb(x, y: integer): boolean; virtual;
    procedure KeyDown(var Key: word; Shift: TShiftState); override;
    procedure MouseDown(Button: TMouseButton; Shift: TShiftState; x, y: integer); override;
    procedure MouseUp(Button: TMouseButton; Shift: TShiftState; x, y: integer); override;
    procedure MouseMove(Shift: TShiftState; x, y: integer); override;
    { Overrides }
    procedure PaintBuffer; override;
    { Event }
    procedure MouseLeave; override;
    procedure HasFocus; override;
    procedure KillFocus; override;
    { Internal Properties }
    property LowThumbPressed: boolean read FLowSliding;
    property HighThumbPressed: boolean read FHighSliding;
    property MouseOnLowThumb: boolean read FMouseOnLowThumb;
    property MouseOnHighThumb: boolean read FMouseOnHighThumb;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    procedure Loaded; override;
  published
    property Align;
    property LineSize: Integer read FLineSize write SetLineSize default 1;
    property Max: integer read FMax write SetMax default 10;
    property Min: integer read FMin write SetMin default 0;
    property Orientation: TTrackOrientation read FOrientation write SetOrientation;
    property PageSize: Integer read FPageSize write SetPageSize default 2;
    { Return or set the Left (or Top) slider position }
    property LowPosition: integer read FLowPosition write SetLowPosition default 3;
    { Return or set the Right (or Bottom) slider position }
    property HighPosition: integer read FHighPosition write SetHighPosition default 7;
    property Frequency: Integer read FFrequency write SetFrequency default 1;
    { Determines whether to show right (or bottom) slider }
    property HighThumbVisible: Boolean read FHighThumbVisible write SetHighThumbVisible default true;
    { Determines whether to show left (or top) slider }
    property LowThumbVisible: Boolean read FLowThumbVisible write SetLowThumbVisible default true;
    property TabStop default true;
    { Determines width (or height) of the slider }
    property ThumbWidth: word read FThumbWidth write SetThumbWidth default 8;
    property TickMarks: TTickMark read FTickMarks write SetTickMarks default tmBottomRight;
    property ShowTicks: boolean read FShowTicks write SetShowTicks default true;
    { Determines whether to show highlight area }
    property HighlightInterval: boolean read FHighlightInterval write SetHighlightInterval default true;
    { Occurs when a low slider changed position }
    property OnLowChange: TNotifyEvent read FOnLowChange write FOnLowChange;
    { Occurs when a high slider changed position }
    property OnHighChange: TNotifyEvent read FOnHighChange write FOnHighChange;
  end;

implementation {===============================================================}

uses Extctrls, Dialogs;

{ TKsCustomRangeBar }

constructor TKsCustomRangeBar.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  ControlStyle := ControlStyle + [csCaptureMouse];
  Width := 150;
  Height := 45;
  TabStop := true;

  FMax := 10;
  FMin := 0;
  FLowPosition := 3;
  FHighPosition := 7;
  FOrientation := toHorizontal;
  FThumbWidth := 8;

  FLineSize := 1;
  FPageSize := 2;
  FFrequency := 1;

  FLowThumbVisible := true;
  FHighThumbVisible := true;
  FShowTicks := true;
  FHighlightInterval := true;
  FTickMarks := tmBottomRight;

  FLowSliding := false;
  FHighSliding := false;
  FMouseOnLowThumb := false;
  FMouseOnHighThumb := false;
end;

destructor TKsCustomRangeBar.Destroy;
begin
  inherited Destroy;
end;

procedure TKsCustomRangeBar.Loaded;
begin
  inherited;
  Invalidate;
end;

function TKsCustomRangeBar.CoordinatePosition(ACoord: word): integer;
var
  LScaleWidth, LSideMargin, LThumbWidth: integer;
begin
  LSideMargin := GetSideMargin;
  LThumbWidth := GetThumbWidth;

  if Orientation = toHorizontal then
    LScaleWidth := ClientWidth
  else
    LScaleWidth := ClientHeight;
  LScaleWidth := LScaleWidth - LSideMargin * 2 - LThumbWidth * 2 + 1;

  Result := Round(((FMax - FMin) * (ACoord - LSideMargin - LThumbWidth - 1)) /
    LScaleWidth + FMin);

  if Result < Min then
    Result := Min;
  if Result > Max then
    Result := Max;
end;

function TKsCustomRangeBar.PositionCoordinate(APos: integer): word;
var
  LScaleWidth, LSideMargin, LThumbWidth: integer;
begin
  LSideMargin := GetSideMargin;
  LThumbWidth := GetThumbWidth;

  if Orientation = toHorizontal then
    LScaleWidth := FWidth
  else
    LScaleWidth := FHeight;
    
  LScaleWidth := LScaleWidth - LSideMargin * 2 - LThumbWidth * 2 + 1;

  if FMax - FMin = 0 then
    Result := 0
  else
    Result := LSideMargin + LThumbWidth - 1 + Round(LScaleWidth * (APos - FMin) / (FMax - FMin));
end;

{ Drawing }

procedure TKsCustomRangeBar.PaintBuffer;
var
  R: TRect;
begin
  inherited ;

  if Orientation = toHorizontal then
    DrawTrack(GetSideMargin, FWidth - GetSideMargin,
      PositionCoordinate(LowPosition), PositionCoordinate(HighPosition))
  else
    DrawTrack(GetSideMargin, FHeight - GetSideMargin,
      PositionCoordinate(LowPosition), PositionCoordinate(HighPosition));

  if Focused then
  begin
    R := Rect(0, 0, FWidth, FHeight);
    Buffer.DrawFocusRect(R, ckBlack);
  end;

  DrawTicks;

  DrawLowThumb(PositionCoordinate(LowPosition));
  DrawHighThumb(PositionCoordinate(HighPosition));
end;

procedure TKsCustomRangeBar.DrawTrack(BegCoord, EndCoord, LowPosCoord,
  HighPosCoord: integer);
var
  LTrackRect, IntervalRect, TmpRect: TRect;
  LTopBottmMarign, LThumbWidth, LThumbHeight: integer;
begin
  LThumbWidth := GetThumbWidth;

  LTopBottmMarign := GetTopBottomMargin;
  LThumbHeight := Round(LThumbWidth * CStdRngThumbRatio);

  with LTrackRect do
  begin
    if FOrientation = toHorizontal then
    begin
      Top := LTopBottmMarign;
      if TickMarks = tmTopLeft then
        Top := Top + LThumbWidth div 2
      else
        Top := Top + LThumbWidth div 4;

      if ShowTicks and ((TickMarks = tmTopLeft) or (TickMarks = tmBoth)) then
        Top := Top + 4 + 1;

      if TickMarks = tmBoth then
        Bottom := Top + LThumbHeight - (LThumbWidth div 4) * 2
      else
        Bottom := Top + LThumbHeight - LThumbWidth div 2 - LThumbWidth div 4;

      Left := BegCoord;
      Right := EndCoord;

      IntervalRect.Top := Top;
      IntervalRect.Bottom := Bottom;
      IntervalRect.Left := LowPosCoord;
      IntervalRect.Right := HighPosCoord;
    end
    else
    begin
      Left := LTopBottmMarign;
      if TickMarks = tmTopLeft then
        Left := Left + LThumbWidth div 2
      else
        Left := Left + LThumbWidth div 4;

      if ShowTicks and ((TickMarks = tmTopLeft) or (TickMarks = tmBoth)) then
        Left := Left + 4 + 1;

      if TickMarks = tmBoth then
        Right := Left + LThumbHeight - (LThumbWidth div 4) * 2
      else
        Right := Left + LThumbHeight - LThumbWidth div 2 - LThumbWidth div 4;

      Top := BegCoord;
      Bottom := EndCoord;

      IntervalRect.Top := LowPosCoord;
      IntervalRect.Bottom := HighPosCoord;
      IntervalRect.Left := Left;
      IntervalRect.Right := Right;
    end;
  end;

  if Enabled then
    Buffer.FillRect(LTrackRect, KColor(clBtnHighlight))
  else
    Buffer.FillRect(LTrackRect, KColor(clBtnFace));

  if Enabled and (HighlightInterval) then
    Buffer.FillRect(IntervalRect, KColor(clHighlight));

  TmpRect := RectOffset(LTrackRect, 0);
  Buffer.DrawEdge(TmpRect, KColor(clBtnShadow), KColor(clBtnHighlight));
  TmpRect := RectOffset(LTrackRect, -1);
  Buffer.DrawEdge(TmpRect, KColor(cl3DDkShadow), KColor(cl3DLight));
end;

procedure TKsCustomRangeBar.DrawHighThumb(APosCoord: integer);
var
  Bx, By: integer;
  LThumbWidth, LThumbHeight: integer;
begin
  if not HighThumbVisible then Exit;

  LThumbWidth := GetThumbWidth;
  LThumbHeight := Round(LThumbWidth * CStdRngThumbRatio);
  with Buffer do
    if Orientation = toHorizontal then
    begin
      Bx := APosCoord;
      By := GetTopBottomMargin;
      if ShowTicks and ((TickMarks = tmTopLeft) or (TickMarks = tmBoth)) then
        By := By + 4 + 1;
      case TickMarks of
        tmBottomRight:
          begin
            if HighThumbPressed then
              FillHalftonePolygon([Point(Bx, By),
                Point(Bx + LThumbWidth - 1, By),
                  Point(Bx + LThumbWidth - 1, By + LThumbHeight - LThumbWidth),
                  Point(Bx, By + LThumbHeight - 1)], KColor(clBtnFace), KColor(clBtnhighlight))
            else
              FillPolygon([Point(Bx, By),
                Point(Bx + LThumbWidth - 1, By),
                  Point(Bx + LThumbWidth - 1, By + LThumbHeight - LThumbWidth),
                  Point(Bx, By + LThumbHeight - 1)], KColor(clBtnFace));

            MoveTo(Bx + LThumbWidth - 2, By);
            LineTo(Bx, By, KColor(clBtnHighlight));
            LineTo(Bx, By + LThumbHeight - 2, KColor(clBtnHighlight));

            MoveTo(Bx + LThumbWidth - 2, By + 1);
            LineTo(Bx + LThumbWidth - 2, By + LThumbHeight - LThumbWidth, KColor(clBtnShadow));
            LineTo(Bx - 1, By + LThumbHeight - 1, KColor(clBtnShadow));

            MoveTo(Bx + LThumbWidth - 1, By);
            LineTo(Bx + LThumbWidth - 1, By + LThumbHeight - LThumbWidth, KColor(cl3DDkShadow));
            LineTo(Bx - 1, By + LThumbHeight, KColor(cl3DDkShadow))
          end;
        tmTopLeft:
          begin
            if HighThumbPressed then
              FillHalftonePolygon([Point(Bx, By),
                Point(Bx + LThumbWidth - 1, By + LThumbWidth - 1),
                  Point(Bx + LThumbWidth - 1, By + LThumbHeight - 1),
                  Point(Bx, By + LThumbHeight - 1)], KColor(clBtnFace), KColor(clBtnHighlight))
            else
              FillPolygon([Point(Bx, By),
                Point(Bx + LThumbWidth - 1, By + LThumbWidth - 1),
                  Point(Bx + LThumbWidth - 1, By + LThumbHeight - 1),
                  Point(Bx, By + LThumbHeight - 1)], KColor(clBtnFace));

            MoveTo(Bx, By + 2);
            LineTo(Bx, By + LThumbHeight - 1, KColor(clBtnHighlight));

            MoveTo(Bx, By + 1);
            LineTo(Bx + LThumbWidth - 2, By + LThumbWidth - 1, KColor(clBtnShadow));
            LineTo(Bx + LThumbWidth - 2, By + LThumbHeight - 2, KColor(clBtnShadow));
            LineTo(Bx, By + LThumbHeight - 2, KColor(clBtnShadow));

            MoveTo(Bx, By);
            LineTo(Bx + LThumbWidth - 1, By + LThumbWidth - 1, KColor(cl3DDkShadow));
            LineTo(Bx + LThumbWidth - 1, By + LThumbHeight - 1, KColor(cl3DDkShadow));
            LineTo(Bx - 1, By + LThumbHeight - 1, KColor(cl3DDkShadow));
          end;
        tmBoth: begin
          if HighThumbPressed then
            FillHalftoneRect(Rect(Bx, By, Bx + LThumbWidth, By + LThumbHeight), KColor(clBtnFace), KColor(clBtnHighlight))
          else
            FillRect(Rect(Bx, By, Bx + LThumbWidth, By + LThumbHeight), KColor(clBtnFace));
          DrawEdge(Rect(Bx, By, Bx + LThumbWidth, By + LThumbHeight),
            KColor(clBtnHighlight), ckBlack);
          DrawEdge(Rect(Bx + 1, By + 1, Bx + LThumbWidth - 1, By + LThumbHeight - 1),
            KColor(clBtnFace), KColor(clBtnShadow));
        end;
      end;
    end
    else
    begin
      { Orientation = toVertical }

      Bx := GetTopBottomMargin;
      By := APosCoord;
      if ShowTicks and ((TickMarks = tmTopLeft) or (TickMarks = tmBoth)) then
        Bx := Bx + 4 + 1;
      case TickMarks of
        tmBottomRight:
          begin
            if HighThumbPressed then
              FillHalftonePolygon([Point(Bx, By),
                Point(Bx + LThumbHeight - 1, By),
                Point(Bx + LThumbHeight - LThumbWidth, By + LThumbWidth - 1),
                Point(Bx, By + LThumbWidth - 1)], KColor(clBtnFace), KColor(clBtnHighlight))
            else
              FillPolygon([Point(Bx, By),
                Point(Bx + LThumbHeight - 1, By),
                Point(Bx + LThumbHeight - LThumbWidth, By + LThumbWidth - 1),
                Point(Bx, By + LThumbWidth - 1)], KColor(clBtnFace));

            MoveTo(Bx, By + LThumbWidth - 2);
            LineTo(Bx, By, KColor(clBtnHighlight));
            LineTo(Bx + LThumbHeight - 2, By, KColor(clBtnHighlight));

            MoveTo(Bx + LThumbHeight - 2, By);
            LineTo(Bx + LThumbHeight - LThumbWidth, By + LThumbWidth - 2, KColor(clBtnShadow));
            LineTo(Bx, By + LThumbWidth - 2, KColor(clBtnShadow));

            MoveTo(Bx + LThumbHeight - 1, By);
            LineTo(Bx + LThumbHeight - LThumbWidth, By + LThumbWidth - 1, KColor(cl3DDkShadow));
            LineTo(Bx - 1, By + LThumbWidth - 1, KColor(cl3DDkShadow));
          end;
        tmTopLeft:
          begin
            if HighThumbPressed then
              FillHalftonePolygon([Point(Bx, By),
                Point(Bx + LThumbHeight - 1, By),
                Point(Bx + LThumbHeight - 1, By + LThumbWidth - 1),
                Point(Bx + LThumbWidth - 1, By + LThumbWidth - 1)],
                KColor(clBtnFace), KColor(clBtnHighlight))
            else
              FillPolygon([Point(Bx, By),
                Point(Bx + LThumbHeight - 1, By),
                Point(Bx + LThumbHeight - 1, By + LThumbWidth - 1),
                Point(Bx + LThumbWidth - 1, By + LThumbWidth - 1)],
                KColor(clBtnFace));

            MoveTo(Bx + LThumbHeight - 2, By);
            LineTo(Bx, By, KColor(clBtnHighlight));

            MoveTo(Bx + LThumbHeight - 2, By + 1);
            LineTo(Bx + LThumbHeight - 2, By + LThumbWidth - 2, KColor(clBtnShadow));
            LineTo(Bx + LThumbWidth - 1, By + LThumbWidth - 2, KColor(clBtnShadow));
            LineTo(Bx, By - 1, KColor(clBtnShadow));

            MoveTo(Bx + LThumbHeight - 1, By);
            LineTo(Bx + LThumbHeight - 1, By + LThumbWidth - 1, KColor(cl3DDkShadow));
            LineTo(Bx + LThumbWidth - 1, By + LThumbWidth - 1, KColor(cl3DDkShadow));
            LineTo(Bx - 1, By - 1, KColor(cl3DDkShadow));
          end;
        tmBoth: begin
          if HighThumbPressed then
            FillHalftoneRect(Rect(Bx, By, Bx + LThumbHeight, By + LThumbWidth), KColor(clBtnFace), KColor(clBtnHighlight))
          else
            FillRect(Rect(Bx, By, Bx + LThumbHeight, By + LThumbWidth), KColor(clBtnFace));
          DrawEdge(Rect(Bx, By, Bx + LThumbHeight, By + LThumbWidth),
            KColor(clBtnHighlight), ckBlack);
          DrawEdge(Rect(Bx + 1, By + 1, Bx + LThumbHeight - 1, By + LThumbWidth - 1),
            KColor(clBtnFace), KColor(clBtnShadow));
        end;
      end;
    end;
end;

procedure TKsCustomRangeBar.DrawLowThumb(APosCoord: integer);
var
  Bx, By: integer;
  LThumbWidth, LThumbHeight: integer;
begin
  if not LowThumbVisible then Exit;

  LThumbWidth := GetThumbWidth;
  LThumbHeight := Round(LThumbWidth * CStdRngThumbRatio);
  with Buffer do
    if Orientation = toHorizontal then
    begin
      Bx := APosCoord - LThumbWidth + 1;
      By := GetTopBottomMargin;
      if ShowTicks and ((TickMarks = tmTopLeft) or (TickMarks = tmBoth)) then
        By := By + 4 + 1;
      case TickMarks of
        tmBottomRight:
          begin
            if LowThumbPressed then
              FillHalftonePolygon([Point(Bx, By),
                Point(Bx + LThumbWidth - 1, By),
                Point(Bx + LThumbWidth - 1, By + LThumbHeight - 1),
                Point(Bx, By + LThumbHeight - LThumbWidth)],
                KColor(clBtnFace), KColor(clBtnHighlight))
            else
              FillPolygon([Point(Bx, By),
                Point(Bx + LThumbWidth - 1, By),
                Point(Bx + LThumbWidth - 1, By + LThumbHeight - 1),
                Point(Bx, By + LThumbHeight - LThumbWidth)],
                KColor(clBtnFace));

            MoveTo(Bx + LThumbWidth - 2, By);
            LineTo(Bx, By, KColor(clBtnHighlight));
            LineTo(Bx, By + LThumbHeight - LThumbWidth, KColor(clBtnHighlight));
            LineTo(Bx + LThumbWidth - 1, By + LThumbHeight - 1, KColor(clBtnHighlight));

            MoveTo(Bx + LThumbWidth - 2, By + 1);
            LineTo(Bx + LThumbWidth - 2, By + LThumbHeight - 1, KColor(clBtnShadow));

            MoveTo(Bx + LThumbWidth - 1, By);
            LineTo(Bx + LThumbWidth - 1, By + LThumbHeight, KColor(cl3DDkShadow));
          end;
        tmTopLeft:
          begin
            if LowThumbPressed then
              FillHalftonePolygon([Point(Bx + LThumbWidth - 1, By),
                Point(Bx + LThumbWidth - 1, By + LThumbHeight - 1),
                Point(Bx, By + LThumbHeight - 1),
                Point(Bx, By + LThumbWidth - 1)],
                KColor(clBtnFace), KColor(clBtnHighlight))
            else
              FillPolygon([Point(Bx + LThumbWidth - 1, By),
                Point(Bx + LThumbWidth - 1, By + LThumbHeight - 1),
                Point(Bx, By + LThumbHeight - 1),
                Point(Bx, By + LThumbWidth - 1)],
                KColor(clBtnFace));

            MoveTo(Bx + LThumbWidth - 2, By + 1);
            LineTo(Bx, By + LThumbWidth - 1, KColor(clBtnHighlight));
            LineTo(Bx, By + LThumbHeight, KColor(clBtnHighlight));

            MoveTo(Bx + LThumbWidth - 2, By + 1);
            LineTo(Bx + LThumbWidth - 2, By + LThumbHeight - 2, KColor(clBtnShadow));
            LineTo(Bx, By + LThumbHeight - 2, KColor(clBtnShadow));

            MoveTo(Bx + LThumbWidth - 1, By);
            LineTo(Bx + LThumbWidth - 1, By + LThumbHeight - 1, KColor(cl3DDkShadow));
            LineTo(Bx - 1, By + LThumbHeight - 1, KColor(cl3DDkShadow));
          end;
        tmBoth: begin
          if LowThumbPressed then
            FillHalftoneRect(Rect(Bx, By, Bx + LThumbWidth, By + LThumbHeight), KColor(clBtnFace), KColor(clBtnHighlight))
          else
            FillRect(Rect(Bx, By, Bx + LThumbWidth, By + LThumbHeight), KColor(clBtnFace));
          DrawEdge(Rect(Bx, By, Bx + LThumbWidth, By + LThumbHeight),
            KColor(clBtnHighlight), ckBlack);
          DrawEdge(Rect(Bx + 1, By + 1, Bx + LThumbWidth - 1, By + LThumbHeight - 1),
            KColor(clBtnFace), KColor(clBtnShadow));
        end;
      end;
    end
    else
    begin
      { Orientation = toVertical }
      Bx := GetTopBottomMargin;
      By := APosCoord - LThumbWidth + 1;
      if ShowTicks and ((TickMarks = tmTopLeft) or (TickMarks = tmBoth)) then
        Bx := Bx + 4 + 1;
      case TickMarks of
        tmBottomRight:
          begin
            if LowThumbPressed then
              FillHalftonePolygon([Point(Bx, By),
                Point(Bx + LThumbHeight - LThumbWidth, By),
                Point(Bx + LThumbHeight - 1, By + LThumbWidth - 1),
                Point(Bx, By + LThumbWidth - 1)],
                KColor(clBtnFace), KColor(clBtnHighlight))
            else
              FillPolygon([Point(Bx, By),
                Point(Bx + LThumbHeight - LThumbWidth, By),
                Point(Bx + LThumbHeight - 1, By + LThumbWidth - 1),
                Point(Bx, By + LThumbWidth - 1)],
                KColor(clBtnFace));

            MoveTo(Bx, By + LThumbWidth - 2);
            LineTo(Bx, By, KColor(clBtnHighlight));
            LineTo(Bx + LThumbHeight - LThumbWidth, By, KColor(clBtnHighlight));

            MoveTo(Bx + LThumbHeight - LThumbWidth, By + 1);
            LineTo(Bx + LThumbHeight - 3, By + LThumbWidth - 2, KColor(clBtnShadow));
            LineTo(Bx, By + LThumbWidth - 2, KColor(clBtnShadow));

            MoveTo(Bx + LThumbHeight - LThumbWidth, By);
            LineTo(Bx + LThumbHeight - 1, By + LThumbWidth - 1, KColor(cl3DDkShadow));
            LineTo(Bx - 1, By + LThumbWidth - 1, KColor(cl3DDkShadow));
          end;
        tmTopLeft:
          begin
            if LowThumbPressed then
              FillHalftonePolygon([Point(Bx + LThumbWidth - 1, By),
                Point(Bx + LThumbHeight - 1, By),
                Point(Bx + LThumbHeight - 1, By + LThumbWidth - 1),
                Point(Bx, By + LThumbWidth - 1)],
                KColor(clBtnFace), KColor(clBtnHighlight))
            else
              FillPolygon([Point(Bx + LThumbWidth - 1, By),
                Point(Bx + LThumbHeight - 1, By),
                Point(Bx + LThumbHeight - 1, By + LThumbWidth - 1),
                Point(Bx, By + LThumbWidth - 1)],
                KColor(clBtnFace));

            MoveTo(Bx + 1, By + LThumbWidth - 2);
            LineTo(Bx + LThumbWidth - 1, By, KColor(clBtnHighlight));
            LineTo(Bx + LThumbHeight - 1, By, KColor(clBtnHighlight));

            MoveTo(Bx + LThumbHeight - 2, By + 1);
            LineTo(Bx + LThumbHeight - 2, By + LThumbWidth - 2, KColor(clBtnShadow));
            LineTo(Bx, By + LThumbWidth - 2, KColor(clBtnShadow));

            MoveTo(Bx + LThumbHeight - 1, By);
            LineTo(Bx + LThumbHeight - 1, By + LThumbWidth - 1, KColor(cl3DDkShadow));
            LineTo(Bx - 1, By + LThumbWidth - 1, KColor(cl3DDkShadow));
          end;
        tmBoth:
          begin
            if HighThumbPressed then
              FillHalftoneRect(Rect(Bx, By, Bx + LThumbHeight, By + LThumbWidth), KColor(clBtnFace), KColor(clBtnHighlight))
            else
              FillRect(Rect(Bx, By, Bx + LThumbHeight, By + LThumbWidth), KColor(clBtnFace));
            DrawEdge(Rect(Bx, By, Bx + LThumbHeight, By + LThumbWidth),
              KColor(clBtnHighlight), ckBlack);
            DrawEdge(Rect(Bx + 1, By + 1, Bx + LThumbHeight - 1, By + LThumbWidth - 1),
              KColor(clBtnFace), KColor(clBtnShadow));
          end;
      end;
    end;
end;

procedure TKsCustomRangeBar.DrawTick(Value, Coord: integer;
  TopLeft: boolean);
var
  VCoord: integer;
  LThumbHeight: integer;
begin
  LThumbHeight := Round(GetThumbWidth * CStdRngThumbRatio);
  if TopLeft then
  begin
    VCoord := GetTopBottomMargin + 3;
    with Buffer do
    begin
      if Orientation = toHorizontal then
      begin
        MoveTo(Coord, VCoord);
        if (Value = Min) or (Value = Max) then
          LineTo(Coord, VCoord - 4, ckBlack)
        else
          LineTo(Coord, VCoord - 3, ckBlack);
      end
      else
      begin
        MoveTo(VCoord, Coord);
        if (Value = Min) or (Value = Max) then
          LineTo(VCoord - 4, Coord, ckBlack)
        else
          LineTo(VCoord - 3, Coord, ckBlack);
      end;
    end
  end
  else
  begin
    if TickMarks = tmBoth then
      VCoord := GetTopBottomMargin + 4 + 1 +
        LThumbHeight
    else
      VCoord := GetTopBottomMargin + 1 +
        LThumbHeight;
    with Buffer do
    begin
      if Orientation = toHorizontal then
      begin
        MoveTo(Coord, VCoord);
        if (Value = Min) or (Value = Max) then
          LineTo(Coord, VCoord + 4, ckBlack)
        else
          LineTo(Coord, VCoord + 3, ckBlack);
      end
      else
      begin
        MoveTo(VCoord, Coord);
        if (Value = Min) or (Value = Max) then
          LineTo(VCoord + 4, Coord, ckBlack)
        else
          LineTo(VCoord + 3, Coord, ckBlack);
      end;
    end;
  end;
end;

function TKsCustomRangeBar.PtInHighThumb(x, y: integer): boolean;
var
  Bx, By: integer;
  LThumbWidth, LThumbHeight: integer;
begin
  LThumbWidth := GetThumbWidth;
  LThumbHeight := Round(LThumbWidth * CStdRngThumbRatio);
  if Orientation = toHorizontal then
  begin
    Bx := PositionCoordinate(HighPosition);
    By := GetTopBottomMargin;
    if ShowTicks and ((TickMarks = tmTopLeft) or (TickMarks = tmBoth)) then
      By := By + 4 + 1;
    case TickMarks of
      tmBottomRight:
        Result := PointInPolygon(Point(x, y), [Point(Bx, By),
          Point(Bx + LThumbWidth - 1, By),
            Point(Bx + LThumbWidth - 1, By + LThumbHeight - LThumbWidth),
            Point(Bx, By + LThumbHeight - 1)]);
      tmTopLeft:
        Result := PointInPolygon(Point(x, y),
          [Point(Bx, By), Point(Bx + LThumbWidth - 1, By + LThumbWidth - 1),
            Point(Bx + LThumbWidth - 1, By + LThumbHeight - 1),
              Point(Bx, By + LThumbHeight - 1)]);

      else { tmBoth }
        Result := PtInRect(Rect(Bx, By, Bx + LThumbWidth, By +
          LThumbHeight), Point(x, y));
    end;
  end
  else
  begin
    { Orientation = toVertical }
    Bx := GetTopBottomMargin;
    By := PositionCoordinate(HighPosition);
    if ShowTicks and ((TickMarks = tmTopLeft) or (TickMarks = tmBoth)) then
      Bx := Bx + 4 + 1;
    case TickMarks of
      tmBottomRight:
        Result := PointInPolygon(Point(x, y),
          [Point(Bx, By),
          Point(Bx + LThumbHeight - 1, By),
            Point(Bx + LThumbHeight - LThumbWidth, By + LThumbWidth - 1),
            Point(Bx, By + LThumbWidth - 1)]);

      tmTopLeft:
        Result := PointInPolygon(Point(x, y),
          [Point(Bx, By),
          Point(Bx + LThumbHeight - 1, By),
            Point(Bx + LThumbHeight - 1, By + LThumbWidth - 1),
            Point(Bx + LThumbWidth - 1, By + LThumbWidth - 1)]);

    else
      { tmBoth }
      Result := PtInRect(Rect(Bx, By, Bx + LThumbHeight, By +
        LThumbWidth), Point(x, y));
    end;
  end;
end;

function TKsCustomRangeBar.PtInLowThumb(x, y: integer): boolean;
var
  Bx, By: integer;
  LThumbWidth, LThumbHeight: integer;
begin
  LThumbWidth := GetThumbWidth;
  LThumbHeight := Round(LThumbWidth * CStdRngThumbRatio);

  if Orientation = toHorizontal then
  begin
    Bx := PositionCoordinate(LowPosition) - LThumbWidth + 1;
    By := GetTopBottomMargin;
    if ShowTicks and ((TickMarks = tmTopLeft) or (TickMarks = tmBoth)) then
      By := By + 4 + 1;
    case TickMarks of
      tmBottomRight:
        Result := PointInPolygon(Point(x, y),
          [Point(Bx, By),
          Point(Bx + LThumbWidth - 1, By),
            Point(Bx + LThumbWidth - 1, By + LThumbHeight - 1),
            Point(Bx, By + LThumbHeight - LThumbWidth)]);

      tmTopLeft:
        Result := PointInPolygon(Point(x, y),
          [Point(Bx + LThumbWidth - 1, By),
          Point(Bx + LThumbWidth - 1, By + LThumbHeight - 1),
            Point(Bx, By + LThumbHeight - 1),
            Point(Bx, By + LThumbWidth - 1)]);

    else
      { tmBoth }
      Result := PtInRect(Rect(Bx, By, Bx + LThumbWidth, By +
        LThumbHeight), Point(x, y));
    end;
  end
  else
  begin
    { Orientation = toVertical }
    Bx := GetTopBottomMargin;
    By := PositionCoordinate(LowPosition) - LThumbWidth + 1;
    if ShowTicks and ((TickMarks = tmTopLeft) or (TickMarks = tmBoth)) then
      Bx := Bx + 4 + 1;
    case TickMarks of
      tmBottomRight:
        Result := PointInPolygon(Point(x, y), [Point(Bx, By),
          Point(Bx + LThumbHeight - LThumbWidth, By),
            Point(Bx + LThumbHeight - 1, By + LThumbWidth - 1),
            Point(Bx, By + LThumbWidth - 1)]);

      tmTopLeft:
        Result := PointInPolygon(Point(x, y),
          [Point(Bx + LThumbWidth - 1, By),
           Point(Bx + LThumbHeight - 1, By),
            Point(Bx + LThumbHeight - 1, By + LThumbWidth - 1),
            Point(Bx, By + LThumbWidth - 1)]);
    else
      { tmBoth }
      Result := PtInRect(Rect(Bx, By, Bx + LThumbHeight, By +
        LThumbWidth), Point(x, y));
    end;
  end;
end;

procedure TKsCustomRangeBar.DrawTicks;
var
  CurPos: integer;
begin
  if ShowTicks then
  begin
    if (TickMarks = tmTopLeft) or (TickMarks = tmBoth) then
      for CurPos := Min to Max do
        if (CurPos mod Frequency = 0) or (CurPos = Min) or (CurPos = Max) then
          DrawTick(CurPos, PositionCoordinate(CurPos), true);
    if (TickMarks = tmBottomRight) or (TickMarks = tmBoth) then
      for CurPos := Min to Max do
        if (CurPos mod Frequency = 0) or (CurPos = Min) or (CurPos = Max) then
          DrawTick(CurPos, PositionCoordinate(CurPos), false);
  end;
end;

function TKsCustomRangeBar.GetSideMargin: integer;
begin
  Result := 9;
end;

function TKsCustomRangeBar.GetTopBottomMargin: integer;
begin
  Result := 7;
end;

function TKsCustomRangeBar.GetThumbWidth: integer;
begin
  Result := FThumbWidth
end;

procedure TKsCustomRangeBar.LowChange;
begin
  if Assigned(FOnLowChange) then
    FOnLowChange(Self);
end;

procedure TKsCustomRangeBar.HighChange;
begin
  if Assigned(FOnLowChange) then
    FOnLowChange(Self);
end;

{ Events }

procedure TKsCustomRangeBar.MouseDown(Button: TMouseButton; Shift: TShiftState;
  x, y: integer);
begin
  inherited MouseDown(Button, Shift, x, y);
  SetFocus;
  if PtInLowThumb(x, y) and (Button = mbLeft) then
    FLowSliding := true
  else
    if PtInHighThumb(x, y) and (Button = mbLeft) then
    FHighSliding := true;
  if (not (FHighSliding or FLowSliding)) and (Button = mbLeft) then
  begin
    if Orientation = toHorizontal then
    begin
      LowPosition := CoordinatePosition(x);
      HighPosition := CoordinatePosition(x);
    end
    else
    begin
      LowPosition := CoordinatePosition(y);
      HighPosition := CoordinatePosition(y);
    end;
  end;
end;

procedure TKsCustomRangeBar.MouseMove(Shift: TShiftState; x, y: integer);
var
  TmpCoord: integer;
  OldLow, OldHigh: boolean;
begin
  inherited MouseMove(Shift, x, y);

  if Orientation = toHorizontal then
  begin
    TmpCoord := x;
    if (x < 0) then
      TmpCoord := 0
    else
      if x > ClientWidth then
      TmpCoord := ClientWidth
  end
  else
  begin
    TmpCoord := y;
    if (y < 0) then
      TmpCoord := 0
    else
      if y > ClientHeight then
      TmpCoord := ClientHeight
  end;

  if FLowSliding then
    LowPosition := CoordinatePosition(TmpCoord + GetThumbWidth div 2);

  if FHighSliding then
    HighPosition := CoordinatePosition(TmpCoord - GetThumbWidth div 2);

  OldLow := FMouseOnLowThumb;
  if PtInLowThumb(x, y) then
    FMouseOnLowThumb := true
  else
    FMouseOnLowThumb := false;

  OldHigh := FMouseOnHighThumb;
  if PtInHighThumb(x, y) then
    FMouseOnHighThumb := true
  else
    FMouseOnHighThumb := false;

  if (FMouseOnLowThumb <> OldLow) or (FMouseOnHighThumb <> OldHigh) then
    Invalidate;
end;

procedure TKsCustomRangeBar.MouseUp(Button: TMouseButton; Shift: TShiftState; x,
  y: integer);
begin
  inherited MouseUp(Button, Shift, x, y);
  if Button = mbLeft then
  begin
    FLowSliding := false;
    FHighSliding := false;
    Invalidate;
  end;
end;

procedure TKsCustomRangeBar.HasFocus;
begin
  inherited;
  Invalidate;
end;

procedure TKsCustomRangeBar.KillFocus;
begin
  inherited;
  Invalidate;
end;

procedure TKsCustomRangeBar.MouseLeave;
begin
  inherited;
  if FMouseOnLowThumb or FMouseOnHighThumb then
  begin
    FMouseOnLowThumb := false;
    FMouseOnHighThumb := false;
    Invalidate;
  end;
end;

procedure TKsCustomRangeBar.KeyDown(var Key: word; Shift: TShiftState);
begin
  inherited KeyDown(Key, Shift);

  if (ssShift in Shift) or (ssCtrl in Shift) then
    case Key of
      VK_PRIOR: HighPosition := HighPosition - PageSize;
      VK_NEXT: HighPosition := HighPosition + PageSize;
      VK_END: HighPosition := Max;
      VK_HOME: HighPosition := Min;
      VK_LEFT: HighPosition := HighPosition - LineSize;
      VK_RIGHT: HighPosition := HighPosition + LineSize;
      VK_UP: HighPosition := HighPosition - LineSize;
      VK_DOWN: HighPosition := HighPosition + LineSize;
    end
  else
    case Key of
      VK_PRIOR: LowPosition := LowPosition - PageSize;
      VK_NEXT: LowPosition := LowPosition + PageSize;
      VK_END: LowPosition := Max;
      VK_HOME: LowPosition := Min;
      VK_LEFT: LowPosition := LowPosition - LineSize;
      VK_RIGHT: LowPosition := LowPosition + LineSize;
      VK_UP: LowPosition := LowPosition - LineSize;
      VK_DOWN: LowPosition := LowPosition + LineSize;
    end;
end;

procedure TKsCustomRangeBar.WMGetDlgCode(var Msg: TWMGetDlgCode);
begin
  inherited;
  Msg.Result := dlgc_WantArrows;
end;

procedure TKsCustomRangeBar.CMEnabledChanged(var Msg: TMessage);
begin
  inherited;
  Invalidate;
end;

procedure TKsCustomRangeBar.SetMax(const Value: integer);
begin
  if FMax <> Value then
  begin
    FMax := Value;
    if HighPosition > FMax then
      HighPosition := FMax;
    Invalidate;
  end;
end;

procedure TKsCustomRangeBar.SetMin(const Value: integer);
begin
  if FMin <> Value then
  begin
    FMin := Value;
    if LowPosition < FMin then
      LowPosition := FMin;
    Invalidate;
  end;
end;

procedure TKsCustomRangeBar.SetOrientation(const Value: TTrackOrientation);
var
  TmpValue: integer;
begin
  if FOrientation <> Value then
  begin
    FOrientation := Value;
    if (csDesigning in ComponentState)
      and (((FOrientation = toHorizontal) and (Height > Width))
      or ((FOrientation = toVertical) and (Height < Width))) then
    begin
      TmpValue := Width;
      Width := Height;
      Height := TmpValue;
    end;
    Invalidate;
  end;
end;

procedure TKsCustomRangeBar.SetShowTicks(const Value: boolean);
begin
  if FShowTicks <> Value then
  begin
    FShowTicks := Value;
    Invalidate;
  end;
end;

procedure TKsCustomRangeBar.SetTickMarks(const Value: TTickMark);
begin
  if Value <> FTickMarks then
  begin
    FTickMarks := Value;
    Invalidate;
  end;
end;

procedure TKsCustomRangeBar.SetLineSize(const Value: Integer);
begin
  if FLineSize <> Value then
    FLineSize := Value;
end;

procedure TKsCustomRangeBar.SetPageSize(const Value: Integer);
begin
  FPageSize := Value;
end;

procedure TKsCustomRangeBar.SetFrequency(const Value: Integer);
begin
  if FFrequency <> Value then
  begin
    FFrequency := Value;
    Invalidate;
  end;
end;

procedure TKsCustomRangeBar.SetHighPosition(const Value: integer);
begin
  if FHighPosition <> Value then
  begin
    if Value > FMax then
      FHighPosition := FMax
    else
      if Value < FMin then
      FHighPosition := FMin
    else
      FHighPosition := Value;
    if FHighPosition < FLowPosition then
      LowPosition := FHighPosition;
    Invalidate;
    HighChange;
  end;
end;

procedure TKsCustomRangeBar.SetLowPosition(const Value: integer);
begin
  if FLowPosition <> Value then
  begin
    if Value > FMax then
      FLowPosition := FMax
    else
      if Value < FMin then
      FLowPosition := FMin
    else
      FLowPosition := Value;
    if FLowPosition > FHighPosition then
      HighPosition := FLowPosition;
    Invalidate;
    LowChange;
  end;
end;

procedure TKsCustomRangeBar.SetHighThumbVisible(const Value: Boolean);
begin
  if FHighThumbVisible <> Value then
  begin
    FHighThumbVisible := Value;
    Invalidate;
  end;
end;

procedure TKsCustomRangeBar.SetLowThumbVisible(const Value: Boolean);
begin
  if FLowThumbVisible <> Value then
  begin
    FLowThumbVisible := Value;
    Invalidate;
  end;
end;

procedure TKsCustomRangeBar.SetHighlightInterval(const Value: boolean);
begin
  if FHighlightInterval <> Value then
  begin
    FHighlightInterval := Value;
    Invalidate;
  end;
end;

procedure TKsCustomRangeBar.SetThumbWidth(const Value: word);
begin
  if FThumbWidth <> Value then
  begin
    FThumbWidth := Value;
    Invalidate;
  end;
end;

end.

