1{%MainUnit gtk2wsstdctrls.pp}
2
3{ Callbacks }
4
5procedure Gtk2WS_MemoChanged({%H-}AGtkTextBuffer: PGtkTextBuffer; WidgetInfo: PWidgetInfo); cdecl;
6var
7  Mess: TLMessage;
8begin
9  EventTrace('Gtk2WS_MemoChanged', WidgetInfo^.LCLObject);
10  if WidgetInfo^.ChangeLock > 0 then
11  begin
12    Dec(WidgetInfo^.ChangeLock);
13    Exit;
14  end;
15  Mess.Msg := CM_TEXTCHANGED;
16  DeliverMessage(WidgetInfo^.LCLObject, Mess);
17end;
18
19procedure Gtk2WS_MemoCutToClip(widget: PGtkWidget; WidgetInfo: PWidgetInfo); cdecl;
20var
21  Mess: TLMessage;
22begin
23  EventTrace('Gtk2WS_MemoCutToClip', WidgetInfo^.LCLObject);
24  if (Widget=nil) then ;
25  Mess.msg := LM_CUT;
26  DeliverMessage(WidgetInfo^.LCLObject, Mess);
27end;
28
29procedure Gtk2WS_MemoCopyToClip(widget: PGtkWidget; WidgetInfo: PWidgetInfo); cdecl;
30var
31  Mess: TLMessage;
32begin
33  EventTrace('Gtk2WS_MemoCopyToClip', WidgetInfo^.LCLObject);
34  if (Widget=nil) then ;
35  Mess.msg := LM_COPY;
36  DeliverMessage(WidgetInfo^.LCLObject, Mess);
37end;
38
39procedure Gtk2WS_MemoPasteFromClip(widget: PGtkWidget; WidgetInfo: PWidgetInfo); cdecl;
40var
41  Mess: TLMessage;
42begin
43  EventTrace('Gtk2WS_MemoPasteFromClip', WidgetInfo^.LCLObject);
44  if (Widget=nil) then ;
45  Mess.msg := LM_PASTE;
46  DeliverMessage(WidgetInfo^.LCLObject, Mess);
47  g_object_set_data(PGObject(widget), 'lcl-memo-paste-from-clip', gPointer(-1));
48end;
49
50procedure Gtk2WS_MemoTextInserting (Textbuffer: PGtkTextBuffer; StartIter: PGtkTextIter;
51             thetext: pgchar; NewTextLength: gint;  WidgetInfo: PWidgetInfo); cdecl;
52var
53  BeginIter, EndIter: TGtkTextIter;
54  Memo: TCustomMemo;
55  CurrLength, CutLength: integer;
56begin
57  if g_object_get_data(PGObject(WidgetInfo^.CoreWidget), 'lcl-memo-paste-from-clip') <> nil then
58  begin
59    g_object_set_data(PGObject(WidgetInfo^.CoreWidget), 'lcl-memo-paste-from-clip', nil);
60    gtk_text_buffer_get_selection_bounds(TextBuffer, @BeginIter, @EndIter);
61    gtk_text_buffer_delete(TextBuffer, @BeginIter, @EndIter);
62    gtk_text_buffer_insert(TextBuffer, @BeginIter, theText, NewTextLength);
63    g_signal_stop_emission_by_name(PGtkObject(Textbuffer), 'insert-text');
64    Exit;
65  end;
66
67  { GTK2 does not provide its own max. length for memos
68    so we have to do our own. }
69
70  if TControl(WidgetInfo^.LCLObject) is TCustomMemo then
71  begin
72    Memo := TCustomMemo(WidgetInfo^.LCLObject);
73    if Memo.MaxLength <= 0 then Exit;
74
75    CurrLength := gtk_text_buffer_get_char_count(TextBuffer);
76    if CurrLength + NewTextLength <= Memo.MaxLength then
77      Exit;
78
79    CutLength := CurrLength + NewTextLength - Memo.MaxLength;
80
81    if NewTextLength - CutLength > 0 then
82      gtk_text_buffer_insert(TextBuffer, StartIter, TheText, NewTextLength - CutLength);
83
84    g_signal_stop_emission_by_name(PGtkObject(Textbuffer), 'insert-text');
85  end;
86end;
87
88
89
90{ TGtk2WSCustomMemo }
91
92class procedure TGtk2WSCustomMemo.SetCallbacks(const AGtkWidget: PGtkWidget;
93  const AWidgetInfo: PWidgetInfo);
94var
95  TextBuf: PGtkTextBuffer;
96begin
97  TextBuf := gtk_text_view_get_buffer(PGtkTextView(AWidgetInfo^.CoreWidget));
98
99  //TGtkWSBaseScrollingWinControl.SetCallbacks(AGtkWidget, AWidgetInfo);
100
101  TGtk2WSWinControl.SetCallbacks(PGtkObject(AGtkWidget), TComponent(AWidgetInfo^.LCLObject));
102
103  SignalConnect(PGtkWidget(TextBuf), 'changed', @Gtk2WS_MemoChanged, AWidgetInfo);
104  SignalConnect(PGtkWidget(TextBuf), 'insert-text', @Gtk2WS_MemoTextInserting, AWidgetInfo);
105
106  //SetCallback(LM_ACTIVATE, AGTKObject,ALCLObject);
107
108  SignalConnect(AWidgetInfo^.CoreWidget, 'cut-clipboard', @Gtk2WS_MemoCutToClip, AWidgetInfo);
109  SignalConnect(AWidgetInfo^.CoreWidget, 'copy-clipboard', @Gtk2WS_MemoCopyToClip, AWidgetInfo);
110  SignalConnect(AWidgetInfo^.CoreWidget, 'paste-clipboard', @Gtk2WS_MemoPasteFromClip, AWidgetInfo);
111
112  g_signal_connect_after(PGtkObject(AWidgetInfo^.CoreWidget), 'populate-popup',
113    gtk_signal_func(@gtkDefaultPopupMenuCloseFix), AWidgetInfo);
114end;
115
116class function TGtk2WSCustomMemo.CreateHandle(const AWinControl: TWinControl;
117  const AParams: TCreateParams): TLCLIntfHandle;
118var
119  Widget,
120  TempWidget: PGtkWidget;
121  WidgetInfo: PWidgetInfo;
122  SS:TPoint;
123begin
124  Widget := gtk_scrolled_window_new(nil, nil);
125  Result := TLCLIntfHandle({%H-}PtrUInt(Widget));
126  if Result = 0 then Exit;
127  {$IFDEF DebugLCLComponents}
128  DebugGtkWidgets.MarkCreated(Widget,dbgsName(AWinControl));
129  {$ENDIF}
130
131  WidgetInfo := CreateWidgetInfo({%H-}Pointer(Result), AWinControl, AParams);
132
133  TempWidget := gtk_text_view_new();
134  gtk_container_add(PGtkContainer(Widget), TempWidget);
135
136  GTK_WIDGET_UNSET_FLAGS(PGtkScrolledWindow(Widget)^.hscrollbar, GTK_CAN_FOCUS);
137  GTK_WIDGET_UNSET_FLAGS(PGtkScrolledWindow(Widget)^.vscrollbar, GTK_CAN_FOCUS);
138
139  SS:=Gtk2TranslateScrollStyle(TCustomMemo(AWinControl).ScrollBars);
140  gtk_scrolled_window_set_policy(PGtkScrolledWindow(Widget),SS.X, SS.Y);
141
142  // add border for memo
143  gtk_scrolled_window_set_shadow_type(PGtkScrolledWindow(Widget),
144    BorderStyleShadowMap[TCustomEdit(AWinControl).BorderStyle]);
145
146  SetMainWidget(Widget, TempWidget);
147  GetOrCreateWidgetInfo(Widget)^.CoreWidget := TempWidget;
148
149  gtk_text_buffer_set_text(gtk_text_view_get_buffer(PGtkTextView(TempWidget)), PChar(TCustomMemo(AWinControl).Text), -1);
150  gtk_text_view_set_editable(PGtkTextView(TempWidget), not TCustomMemo(AWinControl).ReadOnly);
151  gtk_text_view_set_justification(PGtkTextView(TempWidget), aGtkJustification[TCustomMemo(AWinControl).Alignment]);
152  if TCustomMemo(AWinControl).WordWrap then
153    gtk_text_view_set_wrap_mode(PGtkTextView(TempWidget), GTK_WRAP_WORD)
154  else
155    gtk_text_view_set_wrap_mode(PGtkTextView(TempWidget), GTK_WRAP_NONE);
156
157  gtk_text_view_set_accepts_tab(PGtkTextView(TempWidget), TCustomMemo(AWinControl).WantTabs);
158
159  gtk_widget_show_all(Widget);
160
161  Set_RC_Name(AWinControl, Widget);
162  if not AWinControl.HandleObjectShouldBeVisible and not (csDesigning in AWinControl.ComponentState) then
163    gtk_widget_hide(Widget);
164  SetCallbacks(Widget, WidgetInfo);
165end;
166
167class function TGtk2WSCustomMemo.GetStrings(const ACustomMemo: TCustomMemo): TStrings;
168var
169  TextView: PGtkTextView;
170begin
171  TextView := PGtkTextView(GetWidgetInfo({%H-}Pointer(ACustomMemo.Handle))^.CoreWidget);
172  Result := TGtk2MemoStrings.Create(TextView, ACustomMemo);
173end;
174
175class procedure TGtk2WSCustomMemo.SetAlignment(const ACustomEdit: TCustomEdit;
176  const AAlignment: TAlignment);
177var
178  TextView: PGtkTextView;
179begin
180  TextView := PGtkTextView(GetWidgetInfo({%H-}Pointer(ACustomEdit.Handle))^.CoreWidget);
181  gtk_text_view_set_justification(TextView, aGtkJustification[AAlignment]);
182end;
183
184
185class procedure TGtk2WSCustomMemo.SetColor(const AWinControl: TWinControl);
186var
187  AWidget: PGTKWidget;
188begin
189  if not WSCheckHandleAllocated(AWinControl, 'SetColor') then
190    Exit;
191  AWidget := {%H-}PGtkWidget(AWinControl.Handle);
192  AWidget := GetOrCreateWidgetInfo(AWidget)^.CoreWidget;
193  Gtk2WidgetSet.SetWidgetColor(AWidget,
194    AWinControl.Font.Color,
195    AWinControl.Color,
196    [GTK_STATE_NORMAL, GTK_STATE_ACTIVE, GTK_STATE_PRELIGHT, GTK_STYLE_BASE]);
197end;
198
199class procedure TGtk2WSCustomMemo.SetFont(const AWinControl: TWinControl; const AFont: TFont);
200var
201  AWidget: PGTKWidget;
202begin
203  if not WSCheckHandleAllocated(AWinControl, 'SetFont') then
204    Exit;
205
206  AWidget:= {%H-}PGtkWidget(AWinControl.Handle);
207  AWidget:= GetWidgetInfo(AWidget)^.CoreWidget;
208
209  if AWidget <> nil then
210  begin
211    Gtk2WidgetSet.SetWidgetColor(AWidget, AFont.color, clNone,
212       [GTK_STATE_NORMAL,GTK_STATE_ACTIVE,GTK_STATE_PRELIGHT,GTK_STATE_SELECTED,
213        GTK_STYLE_TEXT]);
214    Gtk2WidgetSet.SetWidgetFont(AWidget, AFont);
215  end;
216end;
217
218class procedure TGtk2WSCustomMemo.SetSelStart(const ACustomEdit: TCustomEdit;
219  NewStart: integer);
220var
221  MemoStrings: TGtk2MemoStrings;
222begin
223  if not WSCheckHandleAllocated(ACustomEdit, 'SetSelStart') then
224    Exit;
225
226  MemoStrings := TCustomMemo(ACustomEdit).Lines as TGtk2MemoStrings;
227  MemoStrings.QueueCursorMove(NewStart);
228  MemoStrings.QueueSelectLength(0);
229end;
230
231class procedure TGtk2WSCustomMemo.SetSelLength(const ACustomEdit: TCustomEdit;
232  NewLength: integer);
233var
234  MemoStrings: TGtk2MemoStrings;
235begin
236  if not WSCheckHandleAllocated(ACustomEdit, 'SetSelLength') then
237    Exit;
238
239  MemoStrings := TCustomMemo(ACustomEdit).Lines as TGtk2MemoStrings;
240  MemoStrings.QueueSelectLength(NewLength);
241end;
242
243class procedure TGtk2WSCustomMemo.SetWantTabs(const ACustomMemo: TCustomMemo;
244  const NewWantTabs: boolean);
245var
246  TextView: PGtkTextView;
247begin
248  if not WSCheckHandleAllocated(ACustomMemo, 'SetWantTabs') then
249    Exit;
250
251  TextView := PGtkTextView(GetWidgetInfo({%H-}Pointer(ACustomMemo.Handle))^.CoreWidget);
252  gtk_text_view_set_accepts_tab(TextView, NewWantTabs);
253end;
254
255class procedure TGtk2WSCustomMemo.SetEchoMode(const ACustomEdit: TCustomEdit;
256  NewMode: TEchoMode);
257begin
258  // not supported
259end;
260
261class procedure TGtk2WSCustomMemo.SetPasswordChar(
262  const ACustomEdit: TCustomEdit; NewChar: char);
263begin
264  // not supported
265end;
266
267class procedure TGtk2WSCustomMemo.SetWordWrap(const ACustomMemo: TCustomMemo;
268  const NewWordWrap: boolean);
269var
270  TextView: PGtkTextView;
271begin
272  if not WSCheckHandleAllocated(ACustomMemo, 'SetWordWrap') then
273    Exit;
274  TextView := PGtkTextView(GetWidgetInfo({%H-}Pointer(ACustomMemo.Handle))^.CoreWidget);
275  if NewWordWrap then
276    gtk_text_view_set_wrap_mode(PGtkTextView(TextView), GTK_WRAP_WORD)
277  else
278    gtk_text_view_set_wrap_mode(PGtkTextView(TextView), GTK_WRAP_NONE);
279end;
280
281class procedure TGtk2WSCustomMemo.SetCharCase(const ACustomEdit: TCustomEdit;
282  NewCase: TEditCharCase);
283begin
284  // TODO: TGtk2WSCustomMemo.SetCharCase: implement me!
285end;
286
287class procedure TGtk2WSCustomMemo.SetScrollbars(const ACustomMemo: TCustomMemo;
288  const NewScrollbars: TScrollStyle);
289var
290  SS:TPoint;
291  ScrollWidget: PGtkScrolledWindow;
292begin
293  if not WSCheckHandleAllocated(ACustomMemo, 'SetScrollBars') then Exit;
294  SS:=Gtk2TranslateScrollStyle(NewScrollBars);
295  ScrollWidget:={%H-}PGtkScrolledWindow(ACustomMemo.Handle);
296  gtk_scrolled_window_set_policy(ScrollWidget ,SS.X, SS.Y);
297end;
298
299class procedure TGtk2WSCustomMemo.SetMaxLength(const ACustomEdit: TCustomEdit;
300  NewLength: integer);
301var
302  Widget: PGtkWidget;
303begin
304  Widget:={%H-}PGtkWidget(ACustomEdit.Handle);
305  if GtkWidgetIsA(Widget, GTK_TYPE_ENTRY) then
306    gtk_entry_set_max_length(GTK_ENTRY(Widget), guint16(NewLength));
307end;
308
309class procedure TGtk2WSCustomMemo.SetReadOnly(const ACustomEdit: TCustomEdit;
310  NewReadOnly: boolean);
311var
312  TextView: PGtkTextView;
313begin
314  if not WSCheckHandleAllocated(ACustomEdit, 'SetReadOnly') then
315    Exit;
316
317  TextView := PGtkTextView(GetWidgetInfo({%H-}Pointer(ACustomEdit.Handle))^.CoreWidget);
318  if TextView <> nil then
319    gtk_text_view_set_editable(TextView, not NewReadOnly);
320end;
321
322class procedure TGtk2WSCustomMemo.SetSelText(const ACustomEdit: TCustomEdit;
323  const NewSelText: string);
324var
325  MemoStrings: TGtk2MemoStrings;
326  TextBuf: PGtkTextBuffer;
327  SelStart, SelLength, Utf8Len, CutLen: Integer;
328  StartIter, EndIter: TGtkTextIter;
329begin
330  if not WSCheckHandleAllocated(ACustomEdit, 'SetSelText') then
331    Exit;
332  MemoStrings := TCustomMemo(ACustomEdit).Lines as TGtk2MemoStrings;
333  with GetOrCreateWidgetInfo({%H-}Pointer(ACustomEdit.Handle))^ do
334    TextBuf := gtk_text_view_get_buffer(PGtkTextView(CoreWidget));
335  SelStart := GetSelStart(ACustomEdit);
336  SelLength := GetSelLength(ACustomEdit);
337  gtk_text_buffer_get_iter_at_offset(TextBuf, @StartIter, SelStart);
338  if SelLength > 0 then
339  begin
340    gtk_text_buffer_get_iter_at_offset(TextBuf, @EndIter, SelStart + SelLength);
341    MemoStrings.QueueSelectLength(0);
342    gtk_text_buffer_delete(TextBuf, @StartIter, @EndIter);
343  end;
344  Utf8Len := UTF8Length(NewSelText);
345  SelStart := SelStart + Utf8Len;
346  if ACustomEdit.MaxLength > 0 then
347  begin
348    CutLen := gtk_text_buffer_get_char_count(TextBuf) + Utf8Len - ACustomEdit.MaxLength;
349    if CutLen > 0 then
350      Dec(SelStart, CutLen)
351  end;
352  MemoStrings.QueueCursorMove(SelStart);
353  gtk_text_buffer_insert(TextBuf, @StartIter, PChar(NewSelText), -1);
354end;
355
356class procedure TGtk2WSCustomMemo.SetText(const AWinControl: TWinControl; const AText: string);
357var
358  TextBuf: PGtkTextBuffer;
359  StartIter: TGtkTextIter;
360begin
361  if not WSCheckHandleAllocated(AWinControl, 'SetText') then
362    Exit;
363  TextBuf := gtk_text_view_get_buffer(PGtkTextView(GetOrCreateWidgetInfo({%H-}Pointer(AWinControl.Handle))^.CoreWidget));
364  gtk_text_buffer_set_text(TextBuf, PChar(AText), -1);
365  gtk_text_buffer_get_start_iter(TextBuf, @StartIter);
366  gtk_text_buffer_place_cursor(TextBuf, @StartIter);
367end;
368
369class procedure TGtk2WSCustomMemo.GetPreferredSize(
370  const AWinControl: TWinControl; var PreferredWidth, PreferredHeight: integer;
371  WithThemeSpace: Boolean);
372begin
373  GetGTKDefaultWidgetSize(AWinControl,PreferredWidth,PreferredHeight,
374                          WithThemeSpace);
375end;
376
377class function TGtk2WSCustomMemo.GetSelStart(const ACustomEdit: TCustomEdit): integer;
378var
379  MemoStrings: TGtk2MemoStrings;
380  TextView: PGtkTextView;
381  TextBuffer: PGtkTextBuffer;
382  TextMark: PGtkTextMark;
383  TextIter: TGtkTextIter;
384  StartIter, EndIter: TGtkTextIter;
385  StartPos, EndPos: Integer;
386begin
387  Result := 0;
388  if not WSCheckHandleAllocated(ACustomEdit, 'GetSelStart') then
389    Exit;
390
391  MemoStrings := TCustomMemo(ACustomEdit).Lines as TGtk2MemoStrings;
392  Result := MemoStrings.QueueCursorMovePos;
393  if Result > -1 then
394    Exit;
395
396  TextView := PGtkTextView(GetWidgetInfo({%H-}Pointer(ACustomEdit.Handle))^.CoreWidget);
397  TextBuffer := gtk_text_view_get_buffer(TextView);
398  TextMark := gtk_text_buffer_get_insert(TextBuffer);
399  gtk_text_buffer_get_iter_at_mark(TextBuffer, @TextIter, TextMark);
400
401  Result := gtk_text_iter_get_offset(@TextIter);
402  if GetSelLength(ACustomEdit) = 0 then Exit;
403
404  if not gtk_text_buffer_get_selection_bounds(TextBuffer, @StartIter, @EndIter) then Exit;
405
406  StartPos := gtk_text_iter_get_offset(@StartIter);
407  EndPos := gtk_text_iter_get_offset(@EndIter);
408
409  Result := Min(StartPos, EndPos);
410end;
411
412class function TGtk2WSCustomMemo.GetSelLength(const ACustomEdit: TCustomEdit): integer;
413var
414  MemoStrings: TGtk2MemoStrings;
415  TextView: PGtkTextView;
416  TextBuffer: PGtkTextBuffer;
417  StartIter, EndIter: TGtkTextIter;
418begin
419  Result := 0;
420  if not WSCheckHandleAllocated(ACustomEdit, 'GetSelLength') then
421    Exit;
422
423  MemoStrings := TCustomMemo(ACustomEdit).Lines as TGtk2MemoStrings;
424  Result := MemoStrings.QueueSelLength;
425
426  if Result = -1 then
427  begin
428    Result := 0;
429    TextView := PGtkTextView(GetWidgetInfo({%H-}Pointer(ACustomEdit.Handle))^.CoreWidget);
430    TextBuffer := gtk_text_view_get_buffer(TextView);
431    if not gtk_text_buffer_get_selection_bounds(TextBuffer, @StartIter, @EndIter) then Exit;
432
433    Result := Abs(gtk_text_iter_get_offset(@EndIter) - gtk_text_iter_get_offset(@StartIter));
434  end;
435end;
436
437class function TGtk2WSCustomMemo.GetCaretPos(const ACustomEdit: TCustomEdit): TPoint;
438var
439  TextView: PGtkTextView;
440  TextBuffer: PGtkTextBuffer;
441  Offset: Integer;
442  TextIter: TGtkTextIter;
443begin
444  Result := Point(0, 0);
445  if not WSCheckHandleAllocated(ACustomEdit, 'GetCaretPos') then
446    Exit;
447  TextView := PGtkTextView(GetWidgetInfo({%H-}Pointer(ACustomEdit.Handle))^.CoreWidget);
448  TextBuffer := gtk_text_view_get_buffer(TextView);
449  Offset := GetSelStart(ACustomEdit) + GetSelLength(ACustomEdit);
450  gtk_text_buffer_get_iter_at_offset(TextBuffer, @TextIter, Offset);
451
452  Result.X := gtk_text_iter_get_line_offset(@TextIter);
453  Result.Y := gtk_text_iter_get_line(@TextIter);
454end;
455
456class procedure TGtk2WSCustomMemo.SetCaretPos(const ACustomEdit: TCustomEdit;
457  const NewPos: TPoint);
458var
459  TextView: PGtkTextView;
460  TextBuffer: PGtkTextBuffer;
461  TextIter: TGtkTextIter;
462begin
463  if not WSCheckHandleAllocated(ACustomEdit, 'SetCaretPos') then
464    Exit;
465  TextView := PGtkTextView(GetWidgetInfo({%H-}Pointer(ACustomEdit.Handle))^.CoreWidget);
466  TextBuffer := gtk_text_view_get_buffer(TextView);
467
468  if (NewPos.X < 0) or (NewPos.Y < 0)
469    then Exit;
470
471{ this is quicker, but crashes if given invalid coords:
472  gtk_text_buffer_get_iter_at_line_offset(TextBuffer, @TextIter, NewPos.Y, NewPos.X); }
473
474  if (NewPos.Y >= gtk_text_buffer_get_line_count(TextBuffer))
475    then Exit;
476  gtk_text_buffer_get_iter_at_line(TextBuffer, @TextIter, NewPos.Y);
477  if (NewPos.X >= gtk_text_iter_get_chars_in_line(@TextIter))
478    then Exit;
479  gtk_text_iter_set_line_offset(@TextIter, NewPos.X);
480
481  SetSelStart(ACustomEdit, gtk_text_iter_get_offset(@TextIter));
482end;
483