{ $Id: gtk2wsspin.pp 57581 2018-03-31 13:53:03Z juha $}
{
 *****************************************************************************
 *                               Gtk2WSSpin.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.
 *****************************************************************************
}
unit Gtk2WSSpin;

{$mode objfpc}{$H+}

interface

uses
  // RTL
  glib2, gtk2, SysUtils, Classes, Math,
  // LCL
  Controls, LCLType, LCLProc, LMessages, LazUTF8, Spin, StdCtrls,
  // Widgetset
  Gtk2Extra, Gtk2Def, Gtk2WSStdCtrls,
  Gtk2Proc, WSLCLClasses, WSProc, WSSpin;

type

  { TGtk2WSCustomFloatSpinEdit }

  TGtk2WSCustomFloatSpinEdit = class(TWSCustomFloatSpinEdit)
  protected
    class procedure SetCallbacks(const AWidget: PGtkWidget; const AWidgetInfo: PWidgetInfo); virtual;
  published
    class function GetSelStart(const ACustomEdit: TCustomEdit): integer; override;
    class function GetSelLength(const ACustomEdit: TCustomEdit): integer; override;
    class function GetValue(const ACustomFloatSpinEdit: TCustomFloatSpinEdit): Double; override;

    class procedure SetSelStart(const ACustomEdit: TCustomEdit; NewStart: integer); override;
    class procedure SetSelLength(const ACustomEdit: TCustomEdit; NewLength: integer); override;
    class procedure SetReadOnly(const ACustomEdit: TCustomEdit; ReadOnly: boolean); override;

    class procedure UpdateControl(const ACustomFloatSpinEdit: TCustomFloatSpinEdit); override;
    class function CreateHandle(const AWinControl: TWinControl; const AParams: TCreateParams): TLCLIntfHandle; override;
  end;

implementation

function GetGtkFloatSpinEditable(Spin: PGtkSpinButton): PGtkEntry;
begin
  Result:=PGtkEntry(@(Spin^.entry));
end;

function GetSpinGtkEditable(const Spin: TWinControl): PGtkEntry;
begin
  Result:=GetGtkFloatSpinEditable({%H-}PGtkSpinButton(Spin.Handle));
end;

{ TGtk2WSCustomFloatSpinEdit }

class procedure TGtk2WSCustomFloatSpinEdit.SetCallbacks(
  const AWidget: PGtkWidget; const AWidgetInfo: PWidgetInfo);
begin
  TGtk2WSCustomEdit.SetCallbacks(AWidget, AWidgetInfo);
end;

class function TGtk2WSCustomFloatSpinEdit.GetSelStart(const ACustomEdit: TCustomEdit): integer;
var
  Entry: PGtkEntry;
begin
  if not WSCheckHandleAllocated(ACustomEdit, 'GetSelStart') then
    Exit(0);
  Entry := @{%H-}PGtkSpinButton(ACustomEdit.Handle)^.entry;
  Result := Min(Entry^.current_pos, Entry^.selection_bound)
end;

class function TGtk2WSCustomFloatSpinEdit.GetSelLength(const ACustomEdit: TCustomEdit): integer;
var
  AStart, AEnd: gint;
begin
  Result := 0;
  if not WSCheckHandleAllocated(ACustomEdit, 'GetSelLength') then
    Exit;
  if gtk_editable_get_selection_bounds(PGtkEditable(GetSpinGtkEditable(ACustomEdit)), @AStart, @AEnd) then
    Result := Abs(AEnd-AStart);
end;

class function TGtk2WSCustomFloatSpinEdit.GetValue(
  const ACustomFloatSpinEdit: TCustomFloatSpinEdit): Double;
var
  StrValue: String;
  DecSeparator: Char;
begin
  if not WSCheckHandleAllocated(ACustomFloatSpinEdit, 'GetValue') then
    Exit(0);

  // gtk2 have different meaning of value vs text in GtkSpinBox when
  // we are dealing with real FloatSpinEdit. #18679.
  StrValue := StrPas(gtk_entry_get_text({%H-}PGtkEntry(ACustomFloatSpinEdit.Handle)));
  DecSeparator := DefaultFormatSettings.DecimalSeparator;
  if DecSeparator <> '.' then
    StrValue := UTF8StringReplace(StrValue, '.', DecSeparator, [rfReplaceAll]);
  if DecSeparator <> ',' then
    StrValue := UTF8StringReplace(StrValue, ',', DecSeparator, [rfReplaceAll]);
  Result := ACustomFloatSpinEdit.StrToValue(StrValue);
end;

class procedure TGtk2WSCustomFloatSpinEdit.SetSelStart(const ACustomEdit: TCustomEdit;
  NewStart: integer);
begin
  if not WSCheckHandleAllocated(ACustomEdit, 'SetSelStart') then
    Exit;
  gtk_editable_set_position(GetSpinGtkEditable(ACustomEdit), NewStart);
end;

class procedure TGtk2WSCustomFloatSpinEdit.SetSelLength(const ACustomEdit: TCustomEdit;
  NewLength: integer);
var
  Entry: PGtkEntry;
  SelStart: Integer;
begin
  if not WSCheckHandleAllocated(ACustomEdit, 'SetSelLength') then
    Exit;
  Entry := @{%H-}PGtkSpinButton(ACustomEdit.Handle)^.entry;
  SelStart := GetSelStart(ACustomEdit);
  gtk_entry_select_region(Entry,
    SelStart,
    SelStart + NewLength);
end;

class procedure TGtk2WSCustomFloatSpinEdit.SetReadOnly(const ACustomEdit: TCustomEdit; ReadOnly: boolean);
var
  Widget: PGtkWidget;
  AnAdjustment: PGtkAdjustment;
begin
  if not WSCheckHandleAllocated(ACustomEdit, 'SetReadOnly') then
    Exit;
  Widget := {%H-}PGtkWidget(ACustomEdit.Handle);
  if GTK_IS_EDITABLE(Widget) then
    gtk_editable_set_editable(PGtkEditable(Widget), not ReadOnly);

  AnAdjustment:=gtk_spin_button_get_adjustment(GTK_SPIN_BUTTON(Widget));
  if ReadOnly then
  begin
    AnAdjustment^.lower := TCustomFloatSpinEdit(ACustomEdit).Value;
    AnAdjustment^.upper := TCustomFloatSpinEdit(ACustomEdit).Value;
  end
  else
  begin
    if (TCustomFloatSpinEdit(ACustomEdit).MaxValue > TCustomFloatSpinEdit(ACustomEdit).MinValue) then
    begin
      AnAdjustment^.lower := TCustomFloatSpinEdit(ACustomEdit).MinValue;
      AnAdjustment^.upper := TCustomFloatSpinEdit(ACustomEdit).MaxValue;
    end
    else
    begin
      AnAdjustment^.lower := -MaxDouble;
      AnAdjustment^.upper := MaxDouble;
    end;
  end;

  LockOnChange(PgtkObject(Widget), +1);
  try
    gtk_spin_button_update(GTK_SPIN_BUTTON(Widget));
  finally
    LockOnChange(PgtkObject(Widget), -1);
  end;
end;

class procedure TGtk2WSCustomFloatSpinEdit.UpdateControl(
  const ACustomFloatSpinEdit: TCustomFloatSpinEdit);
var
  AnAdjustment: PGtkAdjustment;
  wHandle: HWND;
  SpinWidget: PGtkSpinButton;
  AMin, AMax: Double;
  Mess: TLMessage;
begin
  //DebugLn(['TGtkWSCustomFloatSpinEdit.UpdateControl ',dbgsName(ACustomFloatSpinEdit)]);
  if not WSCheckHandleAllocated(ACustomFloatSpinEdit, 'UpdateControl') then
    Exit;
  wHandle := ACustomFloatSpinEdit.Handle;
  SpinWidget:=GTK_SPIN_BUTTON({%H-}Pointer(wHandle));

  if ACustomFloatSpinEdit.MaxValue > ACustomFloatSpinEdit.MinValue then
  begin
    AMin := ACustomFloatSpinEdit.MinValue;
    AMax := ACustomFloatSpinEdit.MaxValue;
  end else
  begin
    AMin := -MaxDouble;
    AMax := MaxDouble;
  end;

  AnAdjustment:=gtk_spin_button_get_adjustment(SpinWidget);
  if (AnAdjustment^.lower <> AMin)
  or (AnAdjustment^.upper <> AMax) then
  begin
    AnAdjustment^.lower := AMin;
    AnAdjustment^.upper := AMax;
    gtk_adjustment_changed(AnAdjustment);
  end;

  LockOnChange(PgtkObject(SpinWidget), +1);
  try
    gtk_spin_button_set_digits(SpinWidget, ACustomFloatSpinEdit.DecimalPlaces);
    gtk_spin_button_set_value(SpinWidget,ACustomFloatSpinEdit.Value);
    AnAdjustment^.step_increment := ACustomFloatSpinEdit.Increment;
  finally
    LockOnChange(PgtkObject(SpinWidget), -1);
  end;

  SetReadOnly(TCustomEdit(ACustomFloatSpinEdit), ACustomFloatSpinEdit.ReadOnly);

  FillByte(Mess{%H-},SizeOf(Mess),0);
  Mess.Msg := CM_TEXTCHANGED;
  DeliverMessage(ACustomFloatSpinEdit, Mess);
end;

class function TGtk2WSCustomFloatSpinEdit.CreateHandle(
  const AWinControl: TWinControl; const AParams: TCreateParams
  ): TLCLIntfHandle;
var
  Adjustment: PGtkAdjustment;
  Widget: PGtkWidget;
  WidgetInfo: PWidgetInfo;
  Entry: PGtkEntry;
begin
  Adjustment := PGtkAdjustment(gtk_adjustment_new(1, 1, 100, 1, 0,0));
  Widget := gtk_spin_button_new(Adjustment, 1, 0);
  gtk_widget_show_all(Widget);

  {$IFDEF DebugLCLComponents}
  DebugGtkWidgets.MarkCreated(Widget, dbgsName(AWinControl));
  {$ENDIF}
  Result := TLCLIntfHandle({%H-}PtrUInt(Widget));

  WidgetInfo := CreateWidgetInfo(Widget, AWinControl, AParams);
  Set_RC_Name(AWinControl, Widget);
  if not AWinControl.HandleObjectShouldBeVisible and not (csDesigning in AWinControl.ComponentState) then
    gtk_widget_hide(Widget);
  SetCallbacks(Widget, WidgetInfo);
  if Result <> 0 then
  begin
    Entry := GTK_ENTRY(Widget);
    // PGtkEntry(@PGtkSpinButton(Result)^.entry);
    g_object_set(gtk_widget_get_settings(PGtkWidget(Entry)),
      'gtk-entry-select-on-focus', [0, nil]);
  end;
end;

end.
