{ rxDateRangeEditUnit unit

  Copyright (C) 2005-2017 Lagunov Aleksey alexs75@yandex.ru and Lazarus team
  original conception from rx library for Delphi (c)

  This library is free software; you can redistribute it and/or modify it
  under the terms of the GNU Library General Public License as published by
  the Free Software Foundation; either version 2 of the License, or (at your
  option) any later version with the following modification:

  As a special exception, the copyright holders of this library give you
  permission to link this library with independent modules to produce an
  executable, regardless of the license terms of these independent modules,and
  to copy and distribute the resulting executable under terms of your choice,
  provided that you also meet, for each linked independent module, the terms
  and conditions of the license of that module. An independent module is a
  module which is not derived from or based on this library. If you modify
  this library, you may extend this exception to your version of the library,
  but you are not obligated to do so. If you do not wish to do so, delete this
  exception statement from your version.

  This program is distributed in the hope that it will be useful, but WITHOUT
  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License
  for more details.

  You should have received a copy of the GNU Library General Public License
  along with this library; if not, write to the Free Software Foundation,
  Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
}

unit rxDateRangeEditUnit;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, types, Controls, Buttons, StdCtrls, Spin;

type
  TRxDateRangeEditOption = (reoMonth, reoQuarter, reoHalfYear);
  TRxDateRangeEditOptions = set of TRxDateRangeEditOption;

type

  { TRxCustomDateRangeEdit }

  TRxCustomDateRangeEdit = class(TCustomControl)
  private
    FFlat: Boolean;
    FLockCount:integer;
    FOnEditChange: TNotifyEvent;
    FOnEditClick: TNotifyEvent;
    FOnEditEnter: TNotifyEvent;
    FOnEditExit: TNotifyEvent;
    FOptions: TRxDateRangeEditOptions;
    FsbDecYear: TSpeedButton;
    FsbDecMonth: TSpeedButton;
    FsbIncYear: TSpeedButton;
    FsbIncMonth: TSpeedButton;
    FEditYear: TSpinEdit;
    FEditMonth: TComboBox;
    procedure DoIncMonth(Sender: TObject);
    procedure DoIncYear(Sender: TObject);
    procedure DoDecMonth(Sender: TObject);
    procedure DoDecYear(Sender: TObject);
    function GetHalfYear: word;
    function GetMonth: word;
    function GetPeriod: TDateTime;
    function GetPeriodEnd: TDateTime;
    function GetQuarter: word;
    function GetYear: word;
    procedure SetFlat(AValue: Boolean);
    procedure SetHalfYear(AValue: word);
    procedure SetMonth(AValue: word);
    procedure SetOptions(AValue: TRxDateRangeEditOptions);
    procedure SetPeriod(AValue: TDateTime);
    procedure SetQuarter(AValue: word);
    procedure SetYear(AValue: word);
    procedure InternalOnEditChange(Sender: TObject);
    procedure InternalOnEditClick(Sender: TObject);
    procedure InternalOnEditEnter(Sender: TObject);
    procedure InternalOnEditExit(Sender: TObject);
    procedure Lock;
    procedure UnLock;
  protected
    class function GetControlClassDefaultSize: TSize; override;
    procedure FillMonthNames;
    procedure SetAutoSize(AValue: Boolean); override;
    procedure EditChange; virtual;
    procedure EditClick; virtual;
    procedure EditEnter; virtual;
    procedure EditExit; virtual;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;

    property Quarter:word read GetQuarter write SetQuarter;
    property HalfYear:word read GetHalfYear write SetHalfYear;
    property Flat: Boolean read FFlat write SetFlat default False;
    property Year:word read GetYear write SetYear;
    property Month:word read GetMonth write SetMonth;
    property Period:TDateTime read GetPeriod write SetPeriod;
    property PeriodEnd:TDateTime read GetPeriodEnd;
    property Options:TRxDateRangeEditOptions read FOptions write SetOptions default [reoMonth];
    property OnChange: TNotifyEvent read FOnEditChange write FOnEditChange;
    property OnClick: TNotifyEvent read FOnEditClick write FOnEditClick;
    property OnEnter: TNotifyEvent read FOnEditEnter write FOnEditEnter;
    property OnExit: TNotifyEvent read FOnEditExit write FOnEditExit;
  end;

type
  TRxDateRangeEdit = class(TRxCustomDateRangeEdit)
  published
    property Align;
    property Anchors;
    property Autosize default True;
    property BiDiMode;
    property BorderSpacing;
    property BorderStyle default bsNone;
    property Color;
    property Constraints;
    property Cursor;
    property Enabled;
    property Flat;
    property Hint;
    property Month;
    property Options;
    property ParentBiDiMode;
    property ParentColor;
    property ParentFont;
    property PopupMenu;
    property ShowHint;
    property TabOrder;
    property TabStop;
    property Visible;
    property Year;

    property OnChange;
    property OnClick;
    property OnEnter;
    property OnExit;
    property OnMouseWheel;
    property OnMouseWheelDown;
    property OnMouseWheelUp;
  end;

implementation
uses rxdateutil, rxconst;

{ TRxCustomDateRangeEdit }

procedure TRxCustomDateRangeEdit.DoIncMonth(Sender: TObject);
var
  i:integer;
begin
  if FEditMonth.ItemIndex>=0 then
  begin
    i:=PtrInt(FEditMonth.Items.Objects[FEditMonth.ItemIndex]);

    if I in [17, 18] then
    begin
      if i = 18 then
      begin
        i:=17;
        Year:=Year + 1;
      end
      else
        i:=18;
    end
    else
    if i in [13..16] then
    begin
      inc(i);
      if i> 16 then
      begin
        i:=13;
        Year:=Year + 1;
      end;
    end
    else
    begin
      inc(i);
      if i > 12 then
      begin
        i:=1;
        Year:=Year + 1;
      end;
    end;
    FEditMonth.ItemIndex := i - 1;
  end
  else
    FEditMonth.ItemIndex := 0;
  InternalOnEditChange(Self);
end;

procedure TRxCustomDateRangeEdit.DoIncYear(Sender: TObject);
begin
  FEditYear.Value:=FEditYear.Value + 1;
end;

procedure TRxCustomDateRangeEdit.DoDecMonth(Sender: TObject);
var
  i:integer;
begin
  if FEditMonth.ItemIndex>=0 then
  begin
    i:=PtrInt(FEditMonth.Items.Objects[FEditMonth.ItemIndex]);

    if I in [17, 18] then
    begin
      if i = 18 then
      begin
        i:=17;
        Year:=Year - 1;
      end
      else
        i:=18;
    end
    else
    if i in [13..16] then
    begin
      Dec(i);
      if i> 13 then
      begin
        i:=16;
        Year:=Year - 1;
      end;
    end
    else
    begin
      Dec(i);
      if i < 1 then
      begin
        i:=12;
        Year:=Year - 1;
      end;
    end;
    FEditMonth.ItemIndex := i - 1;
  end
  else
    FEditMonth.ItemIndex := 0;
  InternalOnEditChange(Self);
end;

procedure TRxCustomDateRangeEdit.DoDecYear(Sender: TObject);
begin
  FEditYear.Value:=FEditYear.Value - 1;
end;

function TRxCustomDateRangeEdit.GetHalfYear: word;
var
  i:integer;
begin
  Result:=0;
  if reoHalfYear in FOptions then
  begin
    i:=PtrInt(FEditMonth.Items.Objects[FEditMonth.ItemIndex]);
    if i in [17..18] then
      Result:=i - 16;
  end
end;

function TRxCustomDateRangeEdit.GetMonth: word;
var
  i:integer;
begin
  Result:=0;
  if (reoMonth in FOptions) or (FOptions = []) then
  begin
    i:=PtrInt(FEditMonth.Items.Objects[FEditMonth.ItemIndex]);
    if i in [1..12] then
      Result:=i;
  end
end;

function TRxCustomDateRangeEdit.GetPeriod: TDateTime;
var
  i: PtrInt;
  M: Word;
begin
  i:=PtrInt(FEditMonth.Items.Objects[FEditMonth.ItemIndex]);

  if I < 13 then
    M:=Month
  else
  if i in [13..16] then
    M:= (I - 13) * 3 + 1
  else
  if i in [17..18] then
    M:= (I - 17) * 6 + 1;

  Result:=EncodeDate(Year, M, 1);
end;

function TRxCustomDateRangeEdit.GetPeriodEnd: TDateTime;
var
  i: PtrInt;
  M: Integer;
begin
  i:=PtrInt(FEditMonth.Items.Objects[FEditMonth.ItemIndex]);
  if I < 13 then
    M:=Month
  else
  if i in [13..16] then
    M:= (I - 12) * 3
  else
  if i in [17..18] then
    M:= (I - 16) * 6;

  Result:=EncodeDate(Year, M, DaysPerMonth(Year, M))
end;

function TRxCustomDateRangeEdit.GetQuarter: word;
var
  i:integer;
begin
  Result:=0;
  if reoQuarter in FOptions then
  begin
    i:=PtrInt(FEditMonth.Items.Objects[FEditMonth.ItemIndex]);
    if i in [13..16] then
      Result:=i - 12;
  end
end;

function TRxCustomDateRangeEdit.GetYear: word;
begin
  Result:=FEditYear.Value;
end;

procedure TRxCustomDateRangeEdit.SetFlat(AValue: Boolean);
begin
  if FFlat=AValue then Exit;
  FFlat:=AValue;
  FsbDecMonth.Flat:=FFlat;
  FsbDecYear.Flat:=FFlat;
  FsbIncMonth.Flat:=FFlat;
  FsbIncYear.Flat:=FFlat;
end;

procedure TRxCustomDateRangeEdit.SetHalfYear(AValue: word);
begin

end;

procedure TRxCustomDateRangeEdit.SetMonth(AValue: word);
begin
  if (AValue>0) and (AValue < 13) then
    FEditMonth.ItemIndex:=AValue-1;
end;

procedure TRxCustomDateRangeEdit.SetOptions(AValue: TRxDateRangeEditOptions);
begin
  if FOptions=AValue then Exit;
  FOptions:=AValue;
  FillMonthNames;
end;

procedure TRxCustomDateRangeEdit.SetPeriod(AValue: TDateTime);
var
  Y, M, D, Q: word;
  I: Integer;
begin
  DecodeDate(AValue, Y, M, D);
  FEditYear.Value:=Y;

  if reoMonth in FOptions then
    FEditMonth.ItemIndex:=M-1
  else
  if reoQuarter in FOptions then
  begin
    Q:=M div 4;
    for I:=0 to FEditMonth.Items.Count - 1 do
      if FEditMonth.Items.Objects[i] = TObject(Pointer(Q + 13)) then
      begin
        FEditMonth.ItemIndex:=i;
        break;
      end;
  end
  else
  if reoHalfYear in FOptions then
  begin
    Q:=M div 6;
    for I:=0 to FEditMonth.Items.Count - 1 do
      if FEditMonth.Items.Objects[i] = TObject(Pointer(Q + 17)) then
      begin
        FEditMonth.ItemIndex:=i;
        break;
      end;
  end;
end;

procedure TRxCustomDateRangeEdit.SetQuarter(AValue: word);
begin

end;

procedure TRxCustomDateRangeEdit.SetYear(AValue: word);
begin
  FEditYear.Value:=AValue;
end;

procedure TRxCustomDateRangeEdit.InternalOnEditChange(Sender: TObject);
begin
  if FLockCount = 0 then
    EditChange;
end;

procedure TRxCustomDateRangeEdit.InternalOnEditClick(Sender: TObject);
begin
  EditClick;
end;

procedure TRxCustomDateRangeEdit.InternalOnEditEnter(Sender: TObject);
begin
  EditEnter;
end;

procedure TRxCustomDateRangeEdit.InternalOnEditExit(Sender: TObject);
begin
  EditExit;
end;

procedure TRxCustomDateRangeEdit.Lock;
begin
  Inc(FLockCount);
end;

procedure TRxCustomDateRangeEdit.UnLock;
begin
  if FLockCount > 0 then
    Dec(FLockCount)
  else
    InternalOnEditChange(Self);
end;

class function TRxCustomDateRangeEdit.GetControlClassDefaultSize: TSize;
begin
  Result.CX := 80 + 70 + 23 * 4;
  Result.CY := 23;
end;

procedure TRxCustomDateRangeEdit.FillMonthNames;
var
  i: Integer;
begin
  FEditMonth.Items.BeginUpdate;
  FEditMonth.Items.Clear;
  if (reoMonth in FOptions) or (FOptions = []) then
  begin
    for i:=1 to 12 do
      FEditMonth.Items.AddObject(DefaultFormatSettings.LongMonthNames[i], TObject(Pointer(i)));
  end;

  if (reoQuarter in FOptions) or (FOptions = []) then
  begin
    FEditMonth.Items.AddObject(sFirstQuarter, TObject(Pointer(13)));
    FEditMonth.Items.AddObject(sSecondQuarter, TObject(Pointer(14)));
    FEditMonth.Items.AddObject(sThirdQuarter, TObject(Pointer(15)));
    FEditMonth.Items.AddObject(sFourthQuarter, TObject(Pointer(16)));
  end;

  if (reoHalfYear in FOptions) or (FOptions = []) then
  begin
    FEditMonth.Items.AddObject(sFirstHalfOfYear, TObject(Pointer(17)));
    FEditMonth.Items.AddObject(sSecondHalfOfYear, TObject(Pointer(18)));
  end;
  FEditMonth.ItemIndex:=0;
  FEditMonth.Items.EndUpdate;
end;

procedure TRxCustomDateRangeEdit.SetAutoSize(AValue: Boolean);
begin
  if AutoSize = AValue then
    Exit;
  inherited SetAutosize(AValue);
  FEditMonth.AutoSize := AValue;
  FEditYear.AutoSize := AValue;
end;

procedure TRxCustomDateRangeEdit.EditChange;
begin
  if Assigned(FOnEditChange) then FOnEditChange(Self);
end;

procedure TRxCustomDateRangeEdit.EditClick;
begin
  if Assigned(FOnEditClick) then FOnEditClick(Self);
end;

procedure TRxCustomDateRangeEdit.EditEnter;
begin
  if Assigned(FOnEditEnter) then FOnEditEnter(Self);
end;

procedure TRxCustomDateRangeEdit.EditExit;
begin
  if Assigned(FOnEditExit) then FOnEditExit(Self);
end;

constructor TRxCustomDateRangeEdit.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FLockCount:=0;
  FOptions:=[reoMonth];

  FEditYear:=TSpinEdit.Create(Self);
  FEditMonth:=TComboBox.Create(Self);
  FEditMonth.Style:=csDropDownList;
  FEditMonth.DropDownCount:=12;
  FEditYear.Width:=70;
  FEditMonth.Width:=100;

  FsbDecYear:=TSpeedButton.Create(Self);
  FsbDecMonth:=TSpeedButton.Create(Self);
  FsbIncYear:=TSpeedButton.Create(Self);
  FsbIncMonth:=TSpeedButton.Create(Self);

  FsbDecYear.OnClick:=@DoDecYear;
  FsbDecMonth.OnClick:=@DoDecMonth;
  FsbIncYear.OnClick:=@DoIncYear;
  FsbIncMonth.OnClick:=@DoIncMonth;


  FEditYear.Parent:=Self;
  FsbDecYear.Parent:=Self;
  FsbDecMonth.Parent:=Self;
  FsbIncYear.Parent:=Self;
  FsbIncMonth.Parent:=Self;
  FEditMonth.Parent:=Self;

  FsbDecYear.Caption:='<<';
  FsbDecMonth.Caption:='<';
  FsbIncYear.Caption:='>>';
  FsbIncMonth.Caption:='>';

  FsbDecYear.Left:=0;
  FsbDecMonth.Left:=23;
  FEditMonth.Left:=46;
  FEditYear.Left:=126;
  FsbIncMonth.Left:=206;
  FsbIncYear.Left:=229;


  ControlStyle := ControlStyle + [csNoFocus];


  FsbDecYear.Align:=alLeft;
  FsbDecMonth.Align:=alLeft;
  FsbIncYear.Align:=alRight;
  FsbIncMonth.Align:=alRight;

  FEditYear.Align:=alRight;
  FEditMonth.Align:=alClient;

  FEditYear.MaxValue:=9999;
  with GetControlClassDefaultSize do
    SetInitialBounds(0, 0, CX, CY);
  FillMonthNames;

  SetPeriod(Now);
  AutoSize := True;

  FEditMonth.OnChange:=@InternalOnEditChange;
  FEditYear.OnChange:=@InternalOnEditChange;

  FEditMonth.OnClick:=@InternalOnEditClick;
  FEditYear.OnClick:=@InternalOnEditClick;

  FEditMonth.OnEnter:=@InternalOnEditEnter;
  FEditYear.OnEnter:=@InternalOnEditEnter;

  FEditMonth.OnExit:=@InternalOnEditExit;
  FEditYear.OnExit:=@InternalOnEditExit;
end;

destructor TRxCustomDateRangeEdit.Destroy;
begin
  inherited Destroy;
end;

end.

