{%MainUnit ../dbctrls.pp}
{
 *****************************************************************************
  This file is part of the Lazarus Component Library (LCL)

  See the file COPYING.modifiedLGPL.txt, included in this distribution,
  for details about the license.
 *****************************************************************************
}

{ TDBNavButton }

destructor TDBNavButton.Destroy;
begin
  inherited Destroy;
end;

{ TDBNavDataLink }

procedure TDBNavDataLink.EditingChanged;
begin
  if FNavigator<>nil then FNavigator.EditingChanged;
end;

procedure TDBNavDataLink.DataSetChanged;
begin
  if FNavigator<>nil then FNavigator.DataChanged;
end;

procedure TDBNavDataLink.ActiveChanged;
begin
  if FNavigator<>nil then FNavigator.ActiveChanged;
end;

constructor TDBNavDataLink.Create(TheNavigator: TDBCustomNavigator);
begin
  inherited Create;
  FNavigator := TheNavigator;
  VisualControl := True;
end;

{ TDBCustomNavigator }

procedure TDBCustomNavigator.DefaultHintsChanged(Sender: TObject);
var
  OldNotify: TNotifyEvent;
begin
  if FDefaultHints.Text = FOriginalHints then
    exit;
  
  OldNotify := TStringList(FHints).OnChange;
  TStringlist(FHints).OnChange := nil;
  TStringList(FDefaultHints).OnChange := nil;
  try
    FHints.Assign(FDefaultHints);
    FDefaultHints.Clear;
    UpdateHints;
  finally
    TStringlist(FHints).OnChange := OldNotify;
  end;
  
end;

function TDBCustomNavigator.GetDataSource: TDataSource;
begin
  Result:=FDataLink.DataSource;
end;

function TDBCustomNavigator.GetHints: TStrings;
begin
  if ([csDesigning,csWriting,csReading]*ComponentState=[csDesigning])
  and (FHints.Count=0) then
    Result:=FDefaultHints
  else
    Result:=FHints;
end;

procedure TDBCustomNavigator.SetDataSource(const AValue: TDataSource);
begin
  if AValue=DataSource then exit;
  ChangeDataSource(Self,FDataLink,AValue);
  if not (csLoading in ComponentState) then
    ActiveChanged;
end;

procedure TDBCustomNavigator.SetDirection(const AValue: TDBNavButtonDirection);
begin
  if FDirection=AValue then exit;
  FDirection:=AValue;
  if not (csLoading in ComponentState) then
    UpdateButtons;
end;

procedure TDBCustomNavigator.SetFlat(const AValue: Boolean);
var
  CurButton: TDBNavButtonType;
begin
  if FFlat=AValue then exit;
  FFlat:=AValue;
  for CurButton:=Low(Buttons) to High(Buttons) do
    Buttons[CurButton].Flat:=AValue;
end;

procedure TDBCustomNavigator.SetHints(const AValue: TStrings);
begin
  if (AValue=FDefaultHints) then exit;
  if (AValue.Text=FDefaultHints.Text) then
    FHints.Clear
  else
    FHints.Assign(AValue);
end;

procedure TDBCustomNavigator.SetImages(AValue: TCustomImageList);
begin
  if FImages = AValue then Exit;
  if FImages <> nil then
    FImages.UnRegisterChanges(FImageChangeLink);
  FImages := AValue;
  if FImages <> nil then
  begin
    FImages.RegisterChanges(FImageChangeLink);
    FImages.FreeNotification(Self);
  end;
  UpdateButtons;   //?????
end;

procedure TDBCustomNavigator.SetOptions(AValue: TDBNavigatorOptions);
begin
  if FOptions=AValue then Exit;
  FOptions:=AValue;
  if not (csLoading in ComponentState) then
    UpdateButtons;
end;

procedure TDBCustomNavigator.SetShowButtonHints(const AValue: boolean);
begin
  if FShowButtonHints=AValue then exit;
  FShowButtonHints:=AValue;
  if not (csLoading in ComponentState) then
    UpdateHints;
end;

procedure TDBCustomNavigator.SetVisibleButtons(const AValue: TDBNavButtonSet);
var
  CurButton: TDBNavButtonType;
begin
  if FVisibleButtons=AValue then exit;
  FVisibleButtons:=AValue;
  for CurButton:=Low(Buttons) to High(Buttons) do
  begin
    Buttons[CurButton].Visible:=CurButton in FVisibleButtons;
    FocusableButtons[CurButton].Visible:=CurButton in FVisibleButtons;
  end;
  if not (csLoading in ComponentState) then
    UpdateButtons;
end;

procedure TDBCustomNavigator.CMGetDataLink(var Message: TLMessage);
begin
  Message.Result := PtrUInt(FDataLink);
end;

procedure TDBCustomNavigator.ImageListChange(Sender: TObject);
begin
  UpdateButtons; //????
end;

procedure TDBCustomNavigator.DataChanged;
var
  PriorEnable, NextEnable: Boolean;
begin
  PriorEnable:=Enabled and FDataLink.Active and not FDataLink.DataSet.BOF;
  NextEnable:=Enabled and FDataLink.Active and not FDataLink.DataSet.EOF;

  Buttons[nbFirst].Enabled:=PriorEnable;
  Buttons[nbPrior].Enabled:=PriorEnable;
  Buttons[nbNext].Enabled:=NextEnable;
  Buttons[nbLast].Enabled:=NextEnable;
  Buttons[nbDelete].Enabled:=Enabled and FDataLink.Active
                    and FDataLink.DataSet.CanModify
                    and (not (FDataLink.DataSet.BOF and FDataLink.DataSet.EOF));

  FocusableButtons[nbFirst].Enabled:=PriorEnable;
  FocusableButtons[nbPrior].Enabled:=PriorEnable;
  FocusableButtons[nbNext].Enabled:=NextEnable;
  FocusableButtons[nbLast].Enabled:=NextEnable;
  FocusableButtons[nbDelete].Enabled:=Buttons[nbDelete].Enabled;
end;

procedure TDBCustomNavigator.EditingChanged;
var
  CanModify: Boolean;
begin
  CanModify:=Enabled and FDataLink.Active and FDataLink.DataSet.CanModify;

  Buttons[nbInsert].Enabled:=CanModify;
  Buttons[nbEdit].Enabled:=CanModify and not FDataLink.Editing;
  Buttons[nbPost].Enabled:=CanModify and FDataLink.Editing;
  Buttons[nbCancel].Enabled:=CanModify and FDataLink.Editing;
  Buttons[nbRefresh].Enabled:=CanModify;

  FocusableButtons[nbInsert].Enabled:=CanModify;
  FocusableButtons[nbEdit].Enabled:=CanModify and not FDataLink.Editing;
  FocusableButtons[nbPost].Enabled:=CanModify and FDataLink.Editing;
  FocusableButtons[nbCancel].Enabled:=CanModify and FDataLink.Editing;
  FocusableButtons[nbRefresh].Enabled:=CanModify;
end;

procedure TDBCustomNavigator.ActiveChanged;
var
  CurButton: TDBNavButtonType;
begin
  if not (Enabled and FDataLink.Active) then
  begin
    for CurButton:=Low(Buttons) to High(Buttons) do
    begin
      Buttons[CurButton].Enabled:=False;
      FocusableButtons[CurButton].Enabled:=False;
    end;
  end
  else
  begin
    DataChanged;
    EditingChanged;
  end;
end;

procedure TDBCustomNavigator.Loaded;
begin
  inherited Loaded;
  UpdateButtons;
  UpdateHints;
  ActiveChanged;
end;

procedure TDBCustomNavigator.Notification(AComponent: TComponent; Operation: TOperation);
begin
  inherited Notification(AComponent, Operation);
  if (Operation=opRemove) then begin
    if (FDataLink<>nil) and (AComponent=DataSource) then
      DataSource:=nil;
    if (AComponent = FImages) then Images := nil;
  end;
end;

procedure TDBCustomNavigator.UpdateButtons;
var
  CurButtonType: TDBNavButtonType;
  CurButton: TDBNavButton;
  CurFocusableButton: TDBNavFocusableButton;
begin
  if FUpdateButtonsLock>0
  then begin
    FUpdateButtonsNeeded:=true;
    exit;
  end;

  DisableAlign;
  FUpdateButtonsNeeded:=false;
  if Direction=nbdHorizontal then
    ChildSizing.Layout:=cclLeftToRightThenTopToBottom
  else
    ChildSizing.Layout:=cclTopToBottomThenLeftToRight;

  // force insertion in predefined order
  for CurButtonType:=Low(Buttons) to High(Buttons) do
  begin
    FreeThenNil(Buttons[CurButtonType]);
    FreeThenNil(FocusableButtons[CurButtonType]);
  end;

  // not-focusable buttons
  for CurButtonType:=Low(Buttons) to High(Buttons) do
  begin
    // create/get button
    if Buttons[CurButtonType]=nil
    then begin
      CurButton:=TDBNavButton.Create(Self);
      CurButton.Name:=DBNavButtonResourceName[CurButtonType];
      Buttons[CurButtonType]:=CurButton;
      if CurButtonType in [nbPrior,nbNext] then
        CurButton.NavStyle:=CurButton.NavStyle+[nsAllowTimer];
      //Load default images
      CurButton.LoadGlyphFromResourceName(hInstance, DBNavButtonResourceName[CurButtonType]);
      //Apply custom images if available
      if Assigned(FImages) and (Ord(CurButtontype) < FImages.Count) then
        FImages.GetBitmap(Ord(CurButtonType), CurButton.Glyph);
      CurButton.NumGlyphs:=1;
      CurButton.Parent:=Self;
      CurButton.OnClick:=@ButtonClickHandler;
      CurButton.ControlStyle := CurButton.ControlStyle + [csNoDesignSelectable];
    end else
      CurButton:=Buttons[CurButtonType];

    // update button properties
    CurButton.Flat:=Flat;
    CurButton.Index:=CurButtonType;
    CurButton.Visible:=CurButtonType in FVisibleButtons;
    if not (navFocusableButtons in FOptions) and CurButton.Visible then
      CurButton.Parent := Self
    else
      CurButton.Parent := nil;
  end;

  // focusable buttons
  for CurButtonType:=Low(Buttons) to High(Buttons) do
  begin
    // create/get button
    if FocusableButtons[CurButtonType]=nil then
    begin
      CurFocusableButton:=TDBNavFocusableButton.Create(Self);
      //CurFocusableButton.Name:=DBNavButtonResourceName[CurButtonType];
      FocusableButtons[CurButtonType]:=CurFocusableButton;
      if CurButtonType in [nbPrior,nbNext] then
        CurFocusableButton.NavStyle:=CurFocusableButton.NavStyle+[nsAllowTimer];
      //Load default images
      CurFocusableButton.LoadGlyphFromResourceName(hInstance, DBNavButtonResourceName[CurButtonType]);
      //Apply custom images if available
      if Assigned(FImages) and (Ord(CurButtontype) < FImages.Count) then
        FImages.GetBitmap(Ord(CurButtonType), CurFocusableButton.Glyph);
      CurFocusableButton.NumGlyphs:=1;
      CurFocusableButton.TabStop := True;
      CurFocusableButton.Parent:=Self;
      CurFocusableButton.OnClick:=@ButtonClickHandler;
      CurFocusableButton.ControlStyle := CurFocusableButton.ControlStyle + [csNoDesignSelectable];
    end else
      CurFocusableButton:=FocusableButtons[CurButtonType];

    // update button properties
    CurFocusableButton.Index:=CurButtonType;
    CurFocusableButton.Visible:=CurButtonType in FVisibleButtons;
    if (navFocusableButtons in FOptions) and CurFocusableButton.Visible then
      CurFocusableButton.Parent := Self
    else
      CurFocusableButton.Parent := nil;
  end;

  EnableAlign;
  ActiveChanged;
end;

procedure TDBCustomNavigator.UpdateHints;
var
  CurButton: TDBNavButtonType;
  DBNavButtonDefaultHint: array[TDBNavButtonType] of string;
  NewHint: string;

  procedure AssignHintsCaptions;
  begin
    DBNavButtonDefaultHint[nbFirst] := rsFirstRecordHint;
    DBNavButtonDefaultHint[nbPrior] := rsPriorRecordHint;
    DBNavButtonDefaultHint[nbNext] := rsNextRecordHint;
    DBNavButtonDefaultHint[nbLast] := rsLastRecordHint;
    DBNavButtonDefaultHint[nbInsert] := rsInsertRecordHint;
    DBNavButtonDefaultHint[nbDelete] := rsDeleteRecordHint;
    DBNavButtonDefaultHint[nbEdit] := rsEditRecordHint;
    DBNavButtonDefaultHint[nbPost] := rsPostRecordHint;
    DBNavButtonDefaultHint[nbCancel] := rsCancelRecordHint;
    DBNavButtonDefaultHint[nbRefresh] := rsRefreshRecordsHint;
  end;

begin
  if (FDefaultHints.Count = 0) then
  begin
    TStringList(FDefaultHints).OnChange := nil;
    AssignHintsCaptions;
    for CurButton := Low(Buttons) to High(Buttons) do
      FDefaultHints.Add(DBNavButtonDefaultHint[CurButton]);
    FOriginalHints := FDefaultHints.Text;
    TStringList(FDefaultHints).OnChange := @DefaultHintsChanged;
  end;
  for CurButton := Low(Buttons) to High(Buttons) do
  begin
    if FHints.Count > Ord(CurButton) then
      NewHint := FHints[Ord(CurButton)]
    else
      NewHint := FDefaultHints[Ord(CurButton)];

    Buttons[CurButton].Hint := NewHint;
    Buttons[CurButton].ShowHint := ShowButtonHints;

    FocusableButtons[CurButton].Hint := NewHint;
    FocusableButtons[CurButton].ShowHint := ShowButtonHints;
  end;
end;

procedure TDBCustomNavigator.HintsChanged(Sender: TObject);
begin
  UpdateHints;
end;

procedure TDBCustomNavigator.ButtonClickHandler(Sender: TObject);
begin
  if sender is TDBNavButton then
    BtnClick(TDBNavButton(Sender).Index)
  else
  if sender is TDBNavFocusableButton then
    BtnClick(TDBNavFocusableButton(sender).Index);
end;

class function TDBCustomNavigator.GetControlClassDefaultSize: TSize;
begin
  Result.CX := 241;
  Result.CY := 25;
end;

procedure TDBCustomNavigator.BeginUpdateButtons;
begin
  inc(FUpdateButtonsLock);
end;

procedure TDBCustomNavigator.EndUpdateButtons;
begin
  dec(FUpdateButtonsLock);
  if (FUpdateButtonsLock<0) then
    RaiseGDBException('TDBCustomNavigator.EndUpdateButtons');
  if (FUpdateButtonsLock=0) then begin
    if FUpdateButtonsNeeded then
      UpdateButtons;
  end;
end;

procedure TDBCustomNavigator.SetEnabled(Value: Boolean);
begin
  if Value<>Enabled then begin
    inherited SetEnabled(Value);
    if not (csLoading in ComponentState) then
      UpdateButtons;
  end;
end;

constructor TDBCustomNavigator.Create(TheOwner: TComponent);
begin
  BeginUpdateButtons;
  inherited Create(TheOwner);
  ControlStyle:=ControlStyle-[csAcceptsControls,csSetCaption]+[csOpaque];
  FDirection:=nbdHorizontal;
  ChildSizing.Layout:=cclLeftToRightThenTopToBottom;
  ChildSizing.ControlsPerLine:=100;
  ChildSizing.EnlargeHorizontal:=crsScaleChilds;
  ChildSizing.ShrinkHorizontal:=crsScaleChilds;
  ChildSizing.EnlargeVertical:=crsScaleChilds;
  ChildSizing.ShrinkVertical:=crsScaleChilds;
  FDataLink:=TDBNavDataLink.Create(Self);
  FVisibleButtons:=DefaultDBNavigatorButtons;
  FHints:=TStringList.Create;
  FShowButtonHints:=true;
  TStringList(FHints).OnChange:=@HintsChanged;
  FDefaultHints:=TStringList.Create;
  FImageChangeLink := TChangeLink.Create;
  FImageChangeLink.OnChange := @ImageListChange;
  BevelOuter:=bvNone;
  BevelInner:=bvNone;
  FConfirmDelete:=True;
  with GetControlClassDefaultSize do
    SetInitialBounds(0, 0, CX, CY);
  UpdateButtons;
  EndUpdateButtons;
  UpdateHints;
end;

destructor TDBCustomNavigator.Destroy;
begin
  FDataLink.Destroy;
  FHints.Destroy;
  FDefaultHints.Destroy;
  FImageChangeLink.Destroy;
  inherited Destroy;
end;

procedure TDBCustomNavigator.BtnClick(Index: TNavigateBtn);
begin
  if (DataSource<>nil) and (DataSource.State<>dsInactive) then begin
    if not (csDesigning in ComponentState) and Assigned(BeforeAction) then
      BeforeAction(Self,Index);
    with DataSource.DataSet do begin
      case Index of
      nbPrior:  Prior;
      nbNext:   Next;
      nbFirst:  First;
      nbLast:   Last;
      nbInsert: Insert;
      nbEdit:   Edit;
      nbCancel: Cancel;
      nbPost:   Post;
      nbRefresh: Refresh;
      nbDelete:
        if (not ConfirmDelete)
        or (MessageDlg(rsDeleteRecord, mtConfirmation, mbOKCancel, 0 )<>
          mrCancel)
        then
          Delete;
      end;
    end;
  end;
  if not (csDesigning in ComponentState) and Assigned(OnClick) then
    OnClick(Self,Index);
end;

function TDBCustomNavigator.VisibleButtonCount: integer;
var
  CurButton: TDBNavButtonType;
begin
  Result:=0;
  for CurButton:=Low(Buttons) to High(Buttons) do
    if CurButton in FVisibleButtons then
      inc(Result);
end;

// included by dbctrls.pas
