1{ $Id$}
2{
3 *****************************************************************************
4 *                               Gtk2WSSpin.pp                               *
5 *                               -------------                               *
6 *                                                                           *
7 *                                                                           *
8 *****************************************************************************
9
10 *****************************************************************************
11  This file is part of the Lazarus Component Library (LCL)
12
13  See the file COPYING.modifiedLGPL.txt, included in this distribution,
14  for details about the license.
15 *****************************************************************************
16}
17unit Gtk2WSSpin;
18
19{$mode objfpc}{$H+}
20
21interface
22
23uses
24  // RTL
25  glib2, gtk2, SysUtils, Classes, Math,
26  // LCL
27  Controls, LCLType, LCLProc, LMessages, LazUTF8, Spin, StdCtrls,
28  // Widgetset
29  Gtk2Extra, Gtk2Def, Gtk2WSStdCtrls,
30  Gtk2Proc, WSLCLClasses, WSProc, WSSpin;
31
32type
33
34  { TGtk2WSCustomFloatSpinEdit }
35
36  TGtk2WSCustomFloatSpinEdit = class(TWSCustomFloatSpinEdit)
37  protected
38    class procedure SetCallbacks(const AWidget: PGtkWidget; const AWidgetInfo: PWidgetInfo); virtual;
39  published
40    class function GetSelStart(const ACustomEdit: TCustomEdit): integer; override;
41    class function GetSelLength(const ACustomEdit: TCustomEdit): integer; override;
42    class function GetValue(const ACustomFloatSpinEdit: TCustomFloatSpinEdit): Double; override;
43
44    class procedure SetSelStart(const ACustomEdit: TCustomEdit; NewStart: integer); override;
45    class procedure SetSelLength(const ACustomEdit: TCustomEdit; NewLength: integer); override;
46    class procedure SetReadOnly(const ACustomEdit: TCustomEdit; ReadOnly: boolean); override;
47
48    class procedure SetEditorEnabled(const ACustomFloatSpinEdit: TCustomFloatSpinEdit; AValue: Boolean); override;
49
50    class procedure UpdateControl(const ACustomFloatSpinEdit: TCustomFloatSpinEdit); override;
51    class function CreateHandle(const AWinControl: TWinControl; const AParams: TCreateParams): TLCLIntfHandle; override;
52  end;
53
54implementation
55
56function GetGtkFloatSpinEditable(Spin: PGtkSpinButton): PGtkEntry;
57begin
58  Result:=PGtkEntry(@(Spin^.entry));
59end;
60
61function GetSpinGtkEditable(const Spin: TWinControl): PGtkEntry;
62begin
63  Result:=GetGtkFloatSpinEditable({%H-}PGtkSpinButton(Spin.Handle));
64end;
65
66{ TGtk2WSCustomFloatSpinEdit }
67
68class procedure TGtk2WSCustomFloatSpinEdit.SetCallbacks(
69  const AWidget: PGtkWidget; const AWidgetInfo: PWidgetInfo);
70begin
71  TGtk2WSCustomEdit.SetCallbacks(AWidget, AWidgetInfo);
72end;
73
74class function TGtk2WSCustomFloatSpinEdit.GetSelStart(const ACustomEdit: TCustomEdit): integer;
75var
76  Entry: PGtkEntry;
77begin
78  if not WSCheckHandleAllocated(ACustomEdit, 'GetSelStart') then
79    Exit(0);
80  Entry := @{%H-}PGtkSpinButton(ACustomEdit.Handle)^.entry;
81  Result := Min(Entry^.current_pos, Entry^.selection_bound)
82end;
83
84class function TGtk2WSCustomFloatSpinEdit.GetSelLength(const ACustomEdit: TCustomEdit): integer;
85var
86  AStart, AEnd: gint;
87begin
88  Result := 0;
89  if not WSCheckHandleAllocated(ACustomEdit, 'GetSelLength') then
90    Exit;
91  if gtk_editable_get_selection_bounds(PGtkEditable(GetSpinGtkEditable(ACustomEdit)), @AStart, @AEnd) then
92    Result := Abs(AEnd-AStart);
93end;
94
95class function TGtk2WSCustomFloatSpinEdit.GetValue(
96  const ACustomFloatSpinEdit: TCustomFloatSpinEdit): Double;
97var
98  StrValue: String;
99  DecSeparator: Char;
100begin
101  if not WSCheckHandleAllocated(ACustomFloatSpinEdit, 'GetValue') then
102    Exit(0);
103
104  // gtk2 have different meaning of value vs text in GtkSpinBox when
105  // we are dealing with real FloatSpinEdit. #18679.
106  StrValue := StrPas(gtk_entry_get_text({%H-}PGtkEntry(ACustomFloatSpinEdit.Handle)));
107  DecSeparator := DefaultFormatSettings.DecimalSeparator;
108  if DecSeparator <> '.' then
109    StrValue := UTF8StringReplace(StrValue, '.', DecSeparator, [rfReplaceAll]);
110  if DecSeparator <> ',' then
111    StrValue := UTF8StringReplace(StrValue, ',', DecSeparator, [rfReplaceAll]);
112  Result := ACustomFloatSpinEdit.StrToValue(StrValue);
113end;
114
115class procedure TGtk2WSCustomFloatSpinEdit.SetSelStart(const ACustomEdit: TCustomEdit;
116  NewStart: integer);
117begin
118  if not WSCheckHandleAllocated(ACustomEdit, 'SetSelStart') then
119    Exit;
120  gtk_editable_set_position(GetSpinGtkEditable(ACustomEdit), NewStart);
121end;
122
123class procedure TGtk2WSCustomFloatSpinEdit.SetSelLength(const ACustomEdit: TCustomEdit;
124  NewLength: integer);
125var
126  Entry: PGtkEntry;
127  SelStart: Integer;
128begin
129  if not WSCheckHandleAllocated(ACustomEdit, 'SetSelLength') then
130    Exit;
131  Entry := @{%H-}PGtkSpinButton(ACustomEdit.Handle)^.entry;
132  SelStart := GetSelStart(ACustomEdit);
133  gtk_entry_select_region(Entry,
134    SelStart,
135    SelStart + NewLength);
136end;
137
138class procedure TGtk2WSCustomFloatSpinEdit.SetReadOnly(const ACustomEdit: TCustomEdit; ReadOnly: boolean);
139var
140  Widget: PGtkWidget;
141  AnAdjustment: PGtkAdjustment;
142  NewReadOnly: Boolean;
143begin
144  if not WSCheckHandleAllocated(ACustomEdit, 'SetReadOnly') then
145    Exit;
146  //Dont unset the edit's ReadOnly if EditorEnabled = False
147  NewReadOnly := ReadOnly or ((ACustomEdit is TCustomFloatSpinEdit) and (not TCustomFloatSpinEdit(ACustomEdit).EditorEnabled));
148  Widget := {%H-}PGtkWidget(ACustomEdit.Handle);
149  if GTK_IS_EDITABLE(Widget) then
150    gtk_editable_set_editable(PGtkEditable(Widget), not NewReadOnly);
151
152  AnAdjustment:=gtk_spin_button_get_adjustment(GTK_SPIN_BUTTON(Widget));
153  if ReadOnly then
154  begin
155    AnAdjustment^.lower := TCustomFloatSpinEdit(ACustomEdit).Value;
156    AnAdjustment^.upper := TCustomFloatSpinEdit(ACustomEdit).Value;
157  end
158  else
159  begin
160    if (TCustomFloatSpinEdit(ACustomEdit).MaxValue > TCustomFloatSpinEdit(ACustomEdit).MinValue) then
161    begin
162      AnAdjustment^.lower := TCustomFloatSpinEdit(ACustomEdit).MinValue;
163      AnAdjustment^.upper := TCustomFloatSpinEdit(ACustomEdit).MaxValue;
164    end
165    else
166    begin
167      AnAdjustment^.lower := -MaxDouble;
168      AnAdjustment^.upper := MaxDouble;
169    end;
170  end;
171
172  LockOnChange(PgtkObject(Widget), +1);
173  try
174    gtk_spin_button_update(GTK_SPIN_BUTTON(Widget));
175  finally
176    LockOnChange(PgtkObject(Widget), -1);
177  end;
178end;
179
180class procedure TGtk2WSCustomFloatSpinEdit.SetEditorEnabled(
181  const ACustomFloatSpinEdit: TCustomFloatSpinEdit; AValue: Boolean);
182var
183  Widget: PGtkWidget;
184begin
185  if not WSCheckHandleAllocated(ACustomFloatSpinEdit, 'SetEditorEnabled') then
186    Exit;
187  Widget := {%H-}PGtkWidget(ACustomFloatSpinEdit.Handle);
188  if GTK_IS_EDITABLE(Widget) then
189    gtk_editable_set_editable(PGtkEditable(Widget), AValue);
190end;
191
192class procedure TGtk2WSCustomFloatSpinEdit.UpdateControl(
193  const ACustomFloatSpinEdit: TCustomFloatSpinEdit);
194var
195  AnAdjustment: PGtkAdjustment;
196  wHandle: HWND;
197  SpinWidget: PGtkSpinButton;
198  AMin, AMax: Double;
199  Mess: TLMessage;
200begin
201  //DebugLn(['TGtkWSCustomFloatSpinEdit.UpdateControl ',dbgsName(ACustomFloatSpinEdit)]);
202  if not WSCheckHandleAllocated(ACustomFloatSpinEdit, 'UpdateControl') then
203    Exit;
204  wHandle := ACustomFloatSpinEdit.Handle;
205  SpinWidget:=GTK_SPIN_BUTTON({%H-}Pointer(wHandle));
206
207  if ACustomFloatSpinEdit.MaxValue > ACustomFloatSpinEdit.MinValue then
208  begin
209    AMin := ACustomFloatSpinEdit.MinValue;
210    AMax := ACustomFloatSpinEdit.MaxValue;
211  end else
212  begin
213    AMin := -MaxDouble;
214    AMax := MaxDouble;
215  end;
216
217  AnAdjustment:=gtk_spin_button_get_adjustment(SpinWidget);
218  if (AnAdjustment^.lower <> AMin)
219  or (AnAdjustment^.upper <> AMax) then
220  begin
221    AnAdjustment^.lower := AMin;
222    AnAdjustment^.upper := AMax;
223    gtk_adjustment_changed(AnAdjustment);
224  end;
225
226  LockOnChange(PgtkObject(SpinWidget), +1);
227  try
228    gtk_spin_button_set_digits(SpinWidget, ACustomFloatSpinEdit.DecimalPlaces);
229    gtk_spin_button_set_value(SpinWidget,ACustomFloatSpinEdit.Value);
230    AnAdjustment^.step_increment := ACustomFloatSpinEdit.Increment;
231  finally
232    LockOnChange(PgtkObject(SpinWidget), -1);
233  end;
234
235  SetReadOnly(TCustomEdit(ACustomFloatSpinEdit), ACustomFloatSpinEdit.ReadOnly);
236
237  FillByte(Mess{%H-},SizeOf(Mess),0);
238  Mess.Msg := CM_TEXTCHANGED;
239  DeliverMessage(ACustomFloatSpinEdit, Mess);
240end;
241
242class function TGtk2WSCustomFloatSpinEdit.CreateHandle(
243  const AWinControl: TWinControl; const AParams: TCreateParams
244  ): TLCLIntfHandle;
245var
246  Adjustment: PGtkAdjustment;
247  Widget: PGtkWidget;
248  WidgetInfo: PWidgetInfo;
249  Entry: PGtkEntry;
250begin
251  Adjustment := PGtkAdjustment(gtk_adjustment_new(1, 1, 100, 1, 0,0));
252  Widget := gtk_spin_button_new(Adjustment, 1, 0);
253  gtk_widget_show_all(Widget);
254
255  {$IFDEF DebugLCLComponents}
256  DebugGtkWidgets.MarkCreated(Widget, dbgsName(AWinControl));
257  {$ENDIF}
258  Result := TLCLIntfHandle({%H-}PtrUInt(Widget));
259
260  WidgetInfo := CreateWidgetInfo(Widget, AWinControl, AParams);
261  Set_RC_Name(AWinControl, Widget);
262  if not AWinControl.HandleObjectShouldBeVisible and not (csDesigning in AWinControl.ComponentState) then
263    gtk_widget_hide(Widget);
264  SetCallbacks(Widget, WidgetInfo);
265  if Result <> 0 then
266  begin
267    Entry := GTK_ENTRY(Widget);
268    // PGtkEntry(@PGtkSpinButton(Result)^.entry);
269    g_object_set(gtk_widget_get_settings(PGtkWidget(Entry)),
270      'gtk-entry-select-on-focus', [0, nil]);
271  end;
272end;
273
274end.
275