View Issue Details

IDProjectCategoryView StatusLast Update
0032630LazarusWidgetsetpublic2020-06-30 11:30
Reporteraccorp Assigned ToJuha Manninen  
PrioritynormalSeverityminorReproducibilityalways
Status closedResolutionfixed 
Product Version1.9 (SVN) 
Summary0032630: [patch] Gtk2 TEdit/TMemo: fix text selection, make OnChange event compatible with LCL-Win32
DescriptionTEdit/TMemo controls from Gtk2 widgetset has number of issues making it imposible to write crossplatform code for text manipulation. See tests.

customedit.diff
* TCustomEdit: prevent OnChange event firing extra time when CharCase is ecLowerCase or ecUpperCase.

gtk2customedit.diff
gtk2custommemo.diff
* multiple changes to fix CaretPos, SelStart, SelLength, SelText and OnChange.
Steps To ReproduceInstall libxtst development package (required by MouseAndKeyInput)
Download, compile and run attached program. Select control (TEdit or TMemo), press Test button.
All tests is ok on LCL-Win32.
Additional InformationTested on:
Windows 7 x86 gtk2-runtime-2.22.0, gtk2-runtime-2.24.10
Xubuntu 16.04 i686 gtk 2.24.30
Ubuntu 12.04 x86_64 gtk 2.24.10
Fedora 26 x86_64 gtk 2.24.31

This solves 0023131, 0024371 and 0030596 for me.
TagsNo tags attached.
Fixed in Revisionr56287, r56288, r56489
LazTarget-
WidgetsetGTK 2
Attached Files

Relationships

related to 0030596 resolvedJuha Manninen error in the keyboard backspace key on linux TEdit 
related to 0024371 resolvedJuha Manninen Wrong usage of SelStart and SelLength in OnChange event only the first time 
related to 0023131 resolvedJuha Manninen [GTK2+Windows] Backspace on TEdit moves cursor one position to the right 
related to 0032602 confirmed TEdit.OnChange event handler is calling twice for same text 
related to 0033225 resolvedJuha Manninen TMemo SelText problems (Lazarus / Linux) 
related to 0037123 assignedJesus Reyes TDBMemo with Linux (GTK) 
related to 0037116 resolvedJuha Manninen TEdit and Backspace 

Activities

accorp

2017-10-30 23:42

reporter  

test_ced.zip (10,170 bytes)

accorp

2017-10-30 23:42

reporter  

customedit.diff (1,509 bytes)   
Index: lcl/include/customedit.inc
===================================================================
--- lcl/include/customedit.inc	(revision 56244)
+++ lcl/include/customedit.inc	(working copy)
@@ -86,6 +86,7 @@
   // Accessibility
   AccessibleRole := larTextEditorSingleline;
   FTextHint := '';
+  FTextChangedLock := False;
 end;
 
 {------------------------------------------------------------------------------
@@ -578,6 +579,8 @@
   SStart, SLen: Integer;
 begin
   //debugln('TCustomEdit.TextChanged ',DbgSName(Self));
+  if FTextChangedLock then
+    Exit;
   if FCharCase in [ecUppercase, ecLowercase] then
   begin
     // use a local variable to reduce amounts of widgetset calls
@@ -592,7 +595,12 @@
       CPos := CaretPos;
       SStart := SelStart;
       SLen := SelLength;
-      Text := Temp;
+      FTextChangedLock := True;
+      try
+        Text := Temp;
+      finally
+        FTextChangedLock := False;
+      end;
       SelStart  := SStart;
       SelLength := SLen;
       CaretPos  := CPos;
Index: lcl/stdctrls.pp
===================================================================
--- lcl/stdctrls.pp	(revision 56244)
+++ lcl/stdctrls.pp	(working copy)
@@ -731,6 +731,7 @@
     FSelStart: integer;
     FTextChangedByRealSetTextCounter: Integer;
     FTextHint: TTranslateString;
+    FTextChangedLock: Boolean;
     procedure ShowEmulatedTextHint(const ForceShow: Boolean = False);
     procedure HideEmulatedTextHint;
     procedure SetAlignment(const AValue: TAlignment);
customedit.diff (1,509 bytes)   

accorp

2017-10-30 23:43

reporter  

gtk2customedit.diff (10,829 bytes)   
Index: lcl/interfaces/gtk2/gtk2callback.inc
===================================================================
--- lcl/interfaces/gtk2/gtk2callback.inc	(revision 56244)
+++ lcl/interfaces/gtk2/gtk2callback.inc	(working copy)
@@ -408,6 +408,43 @@
   end;
 end;
 
+function postpone_changed_signal(Widget: PGtkWidget): GBoolean; cdecl;
+begin
+  Result := gtk_false;
+  g_object_set_data(PGObject(Widget), 'lcl-postpone-changed-signal', nil);
+  gtkchanged_editbox(Widget, GetWidgetInfo(Widget, False)^.LCLObject);
+end;
+
+procedure gtkchanged_editbox_delete_text(Widget: PGtkWidget; AStartPos,
+  AEndPos: gint; data: gPointer); cdecl;
+var
+  id: gPointer;
+begin
+  id := g_object_get_data(PGObject(Widget), 'lcl-postpone-changed-signal');
+  if id = nil then
+  begin
+    {%H-}PtrUInt(id) := g_idle_add(TGtkFunction(@postpone_changed_signal), Widget);
+    g_object_set_data(PGObject(Widget), 'lcl-postpone-changed-signal', id);
+  end;
+end;
+
+procedure gtkchanged_editbox_insert_text(Widget: PGtkWidget; ANewText: gChar;
+  ANewTextLength: gint; APosition: pgint; data: gPointer); cdecl;
+var
+  id: gPointer;
+begin
+  id := g_object_get_data(PGObject(Widget), 'lcl-postpone-changed-signal');
+  if id <> nil then
+  begin
+    g_source_remove({%H-}PtrUInt(id));
+    g_object_set_data(PGObject(Widget), 'lcl-postpone-changed-signal', nil);
+  end;
+  if LockOnChange(PgtkObject(Widget), 0) > 0 then
+    g_object_set_data(PGObject(Widget), 'lcl-lock-changed-signal', gPointer(-1))
+  else
+    g_object_set_data(PGObject(Widget), 'lcl-lock-changed-signal', nil);
+end;
+
 function gtkchanged_editbox( widget: PGtkWidget; data: gPointer) : GBoolean; cdecl;
 var
   Mess : TLMessage;
@@ -419,6 +456,13 @@
   //debugln('gtkchanged_editbox');
   Result := CallBackDefaultReturn;
 
+  if g_object_get_data(PGObject(Widget), 'lcl-lock-changed-signal') <> nil then
+  begin
+    g_object_set_data(PGObject(Widget), 'lcl-lock-changed-signal', nil);
+    Exit;
+  end;
+  if g_object_get_data(PGObject(Widget), 'lcl-postpone-changed-signal') <> nil then
+    Exit;
   if LockOnChange(PgtkObject(Widget),0)>0 then exit;
   {$IFDEF EventTrace}
   EventTrace('changed_editbox', data);
@@ -455,7 +499,6 @@
         begin
           // if we change selstart in OnChange event new cursor pos need to
           // be postponed in TGtk2WSCustomEdit.SetSelStart
-          NeedCursorCheck := True;
           if g_object_get_data(PGObject(Widget),'lcl-gtkentry-pasted-data') <> nil then
           begin
             g_object_set_data(PGObject(Widget),'lcl-gtkentry-pasted-data',nil);
@@ -462,13 +505,10 @@
             gtk_editable_set_position(PGtkEditable(Widget), GStart);
           end else
           begin
-            if gtk_minor_version < 17 then
-            begin
-              g_object_set_data(PGObject(Widget),'lcl-gtkentry-pasted-data',Widget);
-              g_idle_add(@GtkEntryDelayCursorPos, Widget);
-              exit;
-            end else
-              gtk_editable_set_position(PGtkEditable(Widget), GStart + 1);
+            NeedCursorCheck := True;
+            g_object_set_data(PGObject(Widget),'lcl-gtkentry-pasted-data',Widget);
+            g_idle_add(@GtkEntryDelayCursorPos, Widget);
+            exit;
           end;
         end;
       end;
@@ -654,9 +694,20 @@
 function gtkchanged_editbox_delete(widget: PGtkWidget;
   AType: TGtkDeleteType; APos: gint; data: gPointer): GBoolean; cdecl;
 var
- Info: PWidgetInfo;
+  Info: PWidgetInfo;
+  EntryText: PgChar;
 begin
   Result := CallBackDefaultReturn;
+  if GTK_IS_ENTRY(Widget) then
+  begin
+    EntryText := gtk_entry_get_text(PGtkEntry(Widget));
+    if (PGtkEntry(Widget)^.current_pos = PGtkEntry(Widget)^.selection_bound)
+    and (PGtkEntry(Widget)^.current_pos = UTF8Length(EntryText)) then
+    begin
+      g_signal_stop_emission_by_name(Widget, 'delete-from-cursor');
+      Exit;
+    end;
+  end;
   Info := GetWidgetInfo(Widget, False);
   include(Info^.Flags, wwiInvalidEvent);
 end;
Index: lcl/interfaces/gtk2/gtk2proc.pp
===================================================================
--- lcl/interfaces/gtk2/gtk2proc.pp	(revision 56244)
+++ lcl/interfaces/gtk2/gtk2proc.pp	(working copy)
@@ -96,6 +96,10 @@
 function gtkHideCB( {%H-}widget: PGtkWidget; data: gPointer): GBoolean; cdecl;
 function gtkactivateCB(widget: PGtkWidget; data: gPointer): GBoolean; cdecl;
 function gtkchangedCB( widget: PGtkWidget; data: gPointer): GBoolean; cdecl;
+procedure gtkchanged_editbox_delete_text(Widget: PGtkWidget;
+  {%H-}AStartPos, {%H-}AEndPos: gint; {%H-}data: gPointer); cdecl;
+procedure gtkchanged_editbox_insert_text(Widget: PGtkWidget; ANewText: gChar;
+  {%H-}ANewTextLength: gint; {%H-}APosition: pgint; {%H-}data: gPointer); cdecl;
 function gtkchanged_editbox( widget: PGtkWidget; data: gPointer): GBoolean; cdecl;
 function gtkchanged_spinbox(widget: PGtkWidget; data: gPointer): GBoolean; cdecl;
 function gtkchanged_editbox_backspace( widget: PGtkWidget;
Index: lcl/interfaces/gtk2/gtk2widgetset.inc
===================================================================
--- lcl/interfaces/gtk2/gtk2widgetset.inc	(revision 56244)
+++ lcl/interfaces/gtk2/gtk2widgetset.inc	(working copy)
@@ -442,6 +442,8 @@
        begin
          if GTK_IS_ENTRY(gObject) then
          begin
+           ConnectSenderSignal(gObject,'delete-text', @gtkchanged_editbox_delete_text);
+           ConnectSenderSignal(gObject,'insert-text', @gtkchanged_editbox_insert_text);
            ConnectSenderSignal(gObject,'backspace', @gtkchanged_editbox_backspace);
            ConnectSenderSignal(gObject,'delete-from-cursor', @gtkchanged_editbox_delete);
          end;
Index: lcl/interfaces/gtk2/gtk2wsstdctrls.pp
===================================================================
--- lcl/interfaces/gtk2/gtk2wsstdctrls.pp	(revision 56244)
+++ lcl/interfaces/gtk2/gtk2wsstdctrls.pp	(working copy)
@@ -25,7 +25,7 @@
   glib2,  gdk2, gtk2,
   Classes, SysUtils, Math,
   // LCL
-  Controls, Graphics, StdCtrls, LMessages, LCLType, LCLProc, LazUtf8Classes,
+  Controls, Graphics, StdCtrls, LMessages, LCLType, LCLProc, LazUtf8Classes, LazUTF8,
   // Widgetset
   WSControls, WSProc, WSStdCtrls, Gtk2Int, Gtk2Def,
   Gtk2CellRenderer, Gtk2Globals, Gtk2Proc, InterfaceBase,
@@ -202,6 +202,7 @@
     class procedure SetSelStart(const ACustomEdit: TCustomEdit; NewStart: integer); override;
     class procedure SetSelLength(const ACustomEdit: TCustomEdit; NewLength: integer); override;
     class procedure SetText(const AWinControl: TWinControl; const AText: string); override;
+    class procedure SetSelText(const ACustomEdit: TCustomEdit; const NewSelText: string); override;
 
     class procedure GetPreferredSize(const AWinControl: TWinControl;
                         var PreferredWidth, PreferredHeight: integer;
@@ -1106,6 +1107,7 @@
   finally
     LockOnChange(PgtkObject(Widget), -1);
   end;
+  SetSelStart(TCustomEdit(AWinControl), 0);
   {$IFDEF VerboseTWinControlRealText}
   DebugLn(['TGtkWSCustomEdit.SetText SEND TEXTCHANGED message ',DbgSName(AWinControl),' New="',gtk_entry_get_text(PGtkEntry(AWinControl.Handle)),'"']);
   {$ENDIF}
@@ -1118,6 +1120,42 @@
   {$ENDIF}
 end;
 
+class procedure TGtk2WSCustomEdit.SetSelText(const ACustomEdit: TCustomEdit;
+  const NewSelText: string);
+var
+  Widget: PGtkWidget;
+  Entry: PGtkEntry;
+  Text: string;
+  SelStart: Integer;
+  Mess : TLMessage;
+begin
+  if not WSCheckHandleAllocated(ACustomEdit, 'SetSelText') then
+    Exit;
+  Widget:={%H-}PGtkWidget(ACustomEdit.Handle);
+  if GTK_IS_SPIN_BUTTON(Widget) then
+    Entry := @PGtkSpinButton(Widget)^.entry
+  else
+    Entry := PGtkEntry(Widget);
+  Text := gtk_entry_get_text(Entry);
+  SelStart := GetSelStart(ACustomEdit);
+  Text := UTF8Copy(Text, 1, SelStart) + NewSelText +
+    UTF8Copy(Text, SelStart + GetSelLength(ACustomEdit) + 1, MaxInt);
+  SelStart := SelStart + UTF8Length(NewSelText);
+  // some gtk2 versions fire the change event twice
+  // lock the event and send the message afterwards
+  // see bug http://bugs.freepascal.org/view.php?id=14615
+  LockOnChange(PgtkObject(Widget), +1);
+  try
+    gtk_entry_set_text(Entry, PChar(Text));
+  finally
+    LockOnChange(PgtkObject(Widget), -1);
+  end;
+  SetSelStart(ACustomEdit, SelStart);
+  FillByte(Mess{%H-},SizeOf(Mess),0);
+  Mess.Msg := CM_TEXTCHANGED;
+  DeliverMessage(ACustomEdit, Mess);
+end;
+
 class procedure TGtk2WSCustomEdit.SetCharCase(const ACustomEdit: TCustomEdit;
   NewCase: TEditCharCase);
 begin
@@ -1182,16 +1220,22 @@
 class function TGtk2WSCustomEdit.GetCaretPos(const ACustomEdit: TCustomEdit
   ): TPoint;
 var
-  Widget: PGtkWidget;
+  Entry: PGtkEntry;
+  AInfo: PWidgetInfo;
 begin
   Result := Point(0,0);
   if not WSCheckHandleAllocated(ACustomEdit, 'GetCaretPos') then
     Exit;
-  Widget := {%H-}PGtkWidget(ACustomEdit.Handle);
-  Result.X := gtk_editable_get_position(PGtkEditable(Widget));
+  Entry := {%H-}PGtkEntry(ACustomEdit.Handle);
+  if gtk_widget_has_focus(PGtkWidget(Entry)) then
+    Result.X := Max(Entry^.current_pos, Entry^.selection_bound)
+  else begin
+    AInfo := GetWidgetInfo(PGtkWidget(Entry));
+    if AInfo <> nil then
+      Result.X := AInfo^.CursorPos + AInfo^.SelLength;
+  end;
 end;
 
-
 class function TGtk2WSCustomEdit.GetSelStart(const ACustomEdit: TCustomEdit
   ): integer;
 var
@@ -1255,22 +1299,29 @@
 class procedure TGtk2WSCustomEdit.SetCaretPos(const ACustomEdit: TCustomEdit;
   const NewPos: TPoint);
 var
+  NewStart: Integer;
   Entry: PGtkEntry;
   WidgetInfo: PWidgetInfo;
 begin
   if not WSCheckHandleAllocated(ACustomEdit, 'SetCaretPos') then
     Exit;
+  SetSelLength(ACustomEdit, 0);
   Entry := {%H-}PGtkEntry(ACustomEdit.Handle);
-  if GetCaretPos(ACustomEdit).X = NewPos.X then exit;
+  // make gtk2 consistent with others. issue #11802
+  // if GetCaretPos(ACustomEdit).X = NewPos.X then exit;
 
+  if Entry^.text_max_length > 0 then
+    NewStart := Min(NewPos.X, Entry^.text_max_length)
+  else
+    NewStart := Min(NewPos.X, Entry^.text_length);
+  WidgetInfo := GetWidgetInfo(Entry);
+  WidgetInfo^.CursorPos := NewStart;
   if LockOnChange(PgtkObject(Entry),0) > 0 then
   begin
-    WidgetInfo := GetWidgetInfo(Entry);
-    WidgetInfo^.CursorPos := NewPos.X;
     // postpone
     g_idle_add(@gtk2WSDelayedSelStart, WidgetInfo);
   end else
-    gtk_editable_set_position(PGtkEditable(Entry), NewPos.X);
+    gtk_editable_set_position(PGtkEditable(Entry), NewStart);
 end;
 
 class procedure TGtk2WSCustomEdit.SetEchoMode(const ACustomEdit: TCustomEdit;
@@ -1327,6 +1378,7 @@
 begin
   if not WSCheckHandleAllocated(ACustomEdit, 'SetSelStart') then
     Exit;
+  SetSelLength(ACustomEdit, 0);
   Entry := {%H-}PGtkEntry(ACustomEdit.Handle);
   // make gtk2 consistent with others. issue #11802
   // if GetSelStart(ACustomEdit) = NewStart then exit;
gtk2customedit.diff (10,829 bytes)   

accorp

2017-10-30 23:43

reporter  

gtk2custommemo.diff (7,499 bytes)   
Index: lcl/interfaces/gtk2/gtk2memostrings.inc
===================================================================
--- lcl/interfaces/gtk2/gtk2memostrings.inc	(revision 56244)
+++ lcl/interfaces/gtk2/gtk2memostrings.inc	(working copy)
@@ -57,7 +57,7 @@
 
   AStrings.FTimerMove:=0; // to know if this timer is active when destroyed
 
-  if AStrings.FQueueCursorMove = -1 then
+  if AStrings.FQueueCursorMove = -2 then
   begin
     // always scroll so the cursor is visible
     TextMark := gtk_text_buffer_get_insert(AStrings.FGtkBuf);
@@ -71,7 +71,7 @@
   end;
   gtk_text_view_scroll_to_mark(AStrings.FGtkText, TextMark, 0, True, 0, 1);
 
-  AStrings.FQueueCursorMove := 0;
+  AStrings.FQueueCursorMove := -1;
 end;
 
 function UpdateMemoSelLengthCB(AStrings: TGtk2MemoStrings): gboolean; cdecl;
@@ -153,6 +153,7 @@
   if TheOwner = nil then RaiseGDBException(
     'TGtk2MemoStrings.Create Unspecified owner');
   FOwner:=TheOwner;
+  FQueueCursorMove := -1;
   FQueueSelLength := -1;
   FTimerMove := 0;
   FTimerSel := 0;
@@ -231,12 +232,12 @@
       NewLine := S+LineEnding;
   end;
 
-  if FQueueCursorMove = 0 then
+  if FQueueCursorMove = -1 then
   begin
     TextMark := gtk_text_buffer_get_insert(FGtkBuf);
     gtk_text_buffer_get_iter_at_mark(FGtkBuf, @CursorIter, TextMark);
     if gtk_text_iter_equal(@StartIter, @CursorIter) then
-      QueueCursorMove(-1);
+      QueueCursorMove(-2);
   end;
   
   // and finally insert the new text
@@ -245,11 +246,11 @@
 
 procedure TGtk2MemoStrings.SetTextStr(const Value: string);
 begin
-  if (Value <> Text) then
-  begin
+  QueueCursorMove(0);
+  QueueSelectLength(0);
+  if (Value <> '') and (Text <> '') then
     LockOnChange({%H-}PGtkObject(Owner.Handle), 1);
-    gtk_text_buffer_set_text(FGtkBuf, PChar(Value), -1);
-  end;
+  gtk_text_buffer_set_text(FGtkBuf, PChar(Value), -1);
 end;
 
 procedure TGtk2MemoStrings.LoadFromFile(const FileName: string);
@@ -280,7 +281,7 @@
 begin
   // needed because there is a callback that updates the GtkBuffer
   // internally so that it actually knows where the cursor is
-  if FQueueCursorMove = 0 then
+  if FQueueCursorMove = -1 then
     FTimerMove := gtk_timeout_add(0,TGSourceFunc(@UpdateMemoCursorCB), Pointer(Self));
   FQueueCursorMove := APosition;
 end;
Index: lcl/interfaces/gtk2/gtk2wscustommemo.inc
===================================================================
--- lcl/interfaces/gtk2/gtk2wscustommemo.inc	(revision 56244)
+++ lcl/interfaces/gtk2/gtk2wscustommemo.inc	(working copy)
@@ -44,14 +44,26 @@
   if (Widget=nil) then ;
   Mess.msg := LM_PASTE;
   DeliverMessage(WidgetInfo^.LCLObject, Mess);
+  g_object_set_data(PGObject(widget), 'lcl-memo-paste-from-clip', gPointer(-1));
 end;
 
 procedure Gtk2WS_MemoTextInserting (Textbuffer: PGtkTextBuffer; StartIter: PGtkTextIter;
              thetext: pgchar; NewTextLength: gint;  WidgetInfo: PWidgetInfo); cdecl;
 var
+  BeginIter, EndIter: TGtkTextIter;
   Memo: TCustomMemo;
   CurrLength, CutLength: integer;
 begin
+  if g_object_get_data(PGObject(WidgetInfo^.CoreWidget), 'lcl-memo-paste-from-clip') <> nil then
+  begin
+    g_object_set_data(PGObject(WidgetInfo^.CoreWidget), 'lcl-memo-paste-from-clip', nil);
+    gtk_text_buffer_get_selection_bounds(TextBuffer, @BeginIter, @EndIter);
+    gtk_text_buffer_delete(TextBuffer, @BeginIter, @EndIter);
+    gtk_text_buffer_insert(TextBuffer, @BeginIter, theText, NewTextLength);
+    g_signal_stop_emission_by_name(PGtkObject(Textbuffer), 'insert-text');
+    Exit;
+  end;
+
   { GTK2 does not provide its own max. length for memos
     so we have to do our own. }
 
@@ -212,6 +224,7 @@
 
   MemoStrings := TCustomMemo(ACustomEdit).Lines as TGtk2MemoStrings;
   MemoStrings.QueueCursorMove(NewStart);
+  MemoStrings.QueueSelectLength(0);
 end;
 
 class procedure TGtk2WSCustomMemo.SetSelLength(const ACustomEdit: TCustomEdit;
@@ -305,6 +318,40 @@
     gtk_text_view_set_editable(TextView, not NewReadOnly);
 end;
 
+class procedure TGtk2WSCustomMemo.SetSelText(const ACustomEdit: TCustomEdit;
+  const NewSelText: string);
+var
+  MemoStrings: TGtk2MemoStrings;
+  TextBuf: PGtkTextBuffer;
+  SelStart, SelLength, Utf8Len, CutLen: Integer;
+  StartIter, EndIter: TGtkTextIter;
+begin
+  if not WSCheckHandleAllocated(ACustomEdit, 'SetSelText') then
+    Exit;
+  MemoStrings := TCustomMemo(ACustomEdit).Lines as TGtk2MemoStrings;
+  with GetWidgetInfo({%H-}Pointer(ACustomEdit.Handle), True)^ do
+    TextBuf := gtk_text_view_get_buffer(PGtkTextView(CoreWidget));
+  SelStart := GetSelStart(ACustomEdit);
+  SelLength := GetSelLength(ACustomEdit);
+  gtk_text_buffer_get_iter_at_offset(TextBuf, @StartIter, SelStart);
+  if SelLength > 0 then
+  begin
+    gtk_text_buffer_get_iter_at_offset(TextBuf, @EndIter, SelStart + SelLength);
+    MemoStrings.QueueSelectLength(0);
+    gtk_text_buffer_delete(TextBuf, @StartIter, @EndIter);
+  end;
+  Utf8Len := UTF8Length(NewSelText);
+  SelStart := SelStart + Utf8Len;
+  if ACustomEdit.MaxLength > 0 then
+  begin
+    CutLen := gtk_text_buffer_get_char_count(TextBuf) + Utf8Len - ACustomEdit.MaxLength;
+    if CutLen > 0 then
+      Dec(SelStart, CutLen)
+  end;
+  MemoStrings.QueueCursorMove(SelStart);
+  gtk_text_buffer_insert(TextBuf, @StartIter, PChar(NewSelText), Utf8Len);
+end;
+
 class procedure TGtk2WSCustomMemo.SetText(const AWinControl: TWinControl;
   const AText: string);
 var
@@ -344,7 +391,7 @@
 
   MemoStrings := TCustomMemo(ACustomEdit).Lines as TGtk2MemoStrings;
   Result := MemoStrings.QueueCursorMovePos;
-  if Result > 0 then
+  if Result > -1 then
     Exit;
 
   TextView := PGtkTextView(GetWidgetInfo({%H-}Pointer(ACustomEdit.Handle), False)^.CoreWidget);
@@ -392,7 +439,7 @@
 var
   TextView: PGtkTextView;
   TextBuffer: PGtkTextBuffer;
-  TextMark: PGtkTextMark;
+  Offset: Integer;
   TextIter: TGtkTextIter;
 begin
   Result := Point(0, 0);
@@ -400,8 +447,8 @@
     Exit;
   TextView := PGtkTextView(GetWidgetInfo({%H-}Pointer(ACustomEdit.Handle), False)^.CoreWidget);
   TextBuffer := gtk_text_view_get_buffer(TextView);
-  TextMark := gtk_text_buffer_get_insert(TextBuffer);
-  gtk_text_buffer_get_iter_at_mark(TextBuffer, @TextIter, TextMark);
+  Offset := GetSelStart(ACustomEdit) + GetSelLength(ACustomEdit);
+  gtk_text_buffer_get_iter_at_offset(TextBuffer, @TextIter, Offset);
 
   Result.X := gtk_text_iter_get_line_offset(@TextIter);
   Result.Y := gtk_text_iter_get_line(@TextIter);
@@ -432,5 +479,5 @@
     then Exit;
   gtk_text_iter_set_line_offset(@TextIter, NewPos.X);
 
-  gtk_text_buffer_place_cursor(TextBuffer, @TextIter);
+  SetSelStart(ACustomEdit, gtk_text_iter_get_offset(@TextIter));
 end;
Index: lcl/interfaces/gtk2/gtk2wsstdctrls.pp
===================================================================
--- lcl/interfaces/gtk2/gtk2wsstdctrls.pp	(revision 56244)
+++ lcl/interfaces/gtk2/gtk2wsstdctrls.pp	(working copy)
@@ -241,6 +241,7 @@
     class procedure SetCharCase(const {%H-}ACustomEdit: TCustomEdit; {%H-}NewCase: TEditCharCase); override;
     class procedure SetMaxLength(const ACustomEdit: TCustomEdit; NewLength: integer); override;
     class procedure SetReadOnly(const ACustomEdit: TCustomEdit; NewReadOnly: boolean); override;
+    class procedure SetSelText(const ACustomEdit: TCustomEdit; const NewSelText: string); override;
     class procedure SetText(const AWinControl: TWinControl; const AText: string); override;
     class procedure SetScrollbars(const ACustomMemo: TCustomMemo; const NewScrollbars: TScrollStyle); override;
 
gtk2custommemo.diff (7,499 bytes)   

Bart Broersma

2017-11-01 10:47

developer   ~0103839

Last edited: 2017-11-01 10:47

View 2 revisions

I think the proposed patch (in customedit.inc) will cause the same problem as described in 0032602.

accorp

2017-11-02 04:24

reporter   ~0103847

I'm sorry, but I don't see how it might cause a problem. Patch just adds boolean flag to lock extra OnChange when modified text is assigning. It works in my tests.

Bart Broersma

2017-11-03 18:42

developer   ~0103858

Did you test with the attached sample in 0032602?

Juha Manninen

2017-11-05 15:09

developer   ~0103882

Last edited: 2017-11-05 15:09

View 2 revisions

> Did you test with the attached sample in 0032602?

Bart, what kind of problem do you see there? I tested and cannot see any change in behavior?

Bart Broersma

2017-11-05 22:16

developer   ~0103885

I did not test, I theorized.
It TextChanged is called from within TextChanged (if you chante Text in OnChange) then FTextChangedLock will become False, before it is supposed to become False.

For the same reason I changed boolean FTextChangedByRealSetText into integer FTextChangedByRealSetTextCounter.

I need to set up a Linux VM to test the proposed patch.

Juha Manninen

2017-11-05 22:51

developer   ~0103890

Ok, I changed it into an integer counter to be sure.
TCustomEdit stuff committed in r56288, GTK2 stuff committed in r56287.
I mostly cannot reproduce the original problems but nothing got broken either.
Please test everybody.

Bart Broersma

2017-11-06 19:36

developer   ~0103917

Shouldn't the line
try
 Dec(FTextChangingCounter);

be followed by
  if (FTextChangingCounter < 0) then FTextChangingCounter := 0;

Just to make sure that the control does not get locked forever if something unexpected has happened?
(I must admit, I'm not realy sure.)

Bart

accorp

2017-11-06 20:00

reporter   ~0103920

@Bart Broersma

FTextChangedLock was supposed to prevent the situation you described (calling TextChanged from within TextChanged on text assigning at line "Text := Temp;").
Real OnChange is calling later outside try/finnaly block. And since FTextChangedLock cannot be changed inside try/finaly block replacing it with counter makes no sense, "if FTextChangingCounter > 0 then" is exacly same as "if FTextChangedLock then".

Juha Manninen

2017-11-06 22:47

developer   ~0103930

> if (FTextChangingCounter < 0) then FTextChangingCounter := 0;

In what situation would it go negative?
I also think the original Boolean would have been enough.

Bart Broersma

2017-11-07 22:31

developer   ~0103948

Last edited: 2017-11-07 22:31

View 2 revisions

Well, maybe (or probably) I'm wrong (I've been wrong before).
Change it back to a Boolean then.

Ondrej Pokorny

2017-11-10 00:24

developer   ~0103982

Why do we need 2 counters for 2 border case issues? It must be solved in a simpler way.

Juha Manninen

2017-11-24 21:44

developer   ~0104243

Changed to a boolean. Resolving.

Issue History

Date Modified Username Field Change
2017-10-30 23:42 accorp New Issue
2017-10-30 23:42 accorp File Added: test_ced.zip
2017-10-30 23:42 accorp File Added: customedit.diff
2017-10-30 23:43 accorp File Added: gtk2customedit.diff
2017-10-30 23:43 accorp File Added: gtk2custommemo.diff
2017-10-31 09:15 Juha Manninen Relationship added related to 0030596
2017-10-31 09:15 Juha Manninen Relationship added related to 0024371
2017-11-01 10:47 Bart Broersma Note Added: 0103839
2017-11-01 10:47 Bart Broersma Note Edited: 0103839 View Revisions
2017-11-02 04:24 accorp Note Added: 0103847
2017-11-03 18:23 Juha Manninen Relationship added related to 0023131
2017-11-03 18:42 Bart Broersma Note Added: 0103858
2017-11-03 19:10 Juha Manninen Relationship added related to 0032602
2017-11-05 15:07 Juha Manninen Assigned To => Juha Manninen
2017-11-05 15:07 Juha Manninen Status new => assigned
2017-11-05 15:09 Juha Manninen Note Added: 0103882
2017-11-05 15:09 Juha Manninen Note Edited: 0103882 View Revisions
2017-11-05 22:16 Bart Broersma Note Added: 0103885
2017-11-05 22:51 Juha Manninen Fixed in Revision => r56287, r56288
2017-11-05 22:51 Juha Manninen LazTarget => -
2017-11-05 22:51 Juha Manninen Note Added: 0103890
2017-11-05 22:51 Juha Manninen Status assigned => resolved
2017-11-05 22:51 Juha Manninen Resolution open => fixed
2017-11-06 19:36 Bart Broersma Note Added: 0103917
2017-11-06 19:36 Bart Broersma Status resolved => assigned
2017-11-06 19:36 Bart Broersma Resolution fixed => reopened
2017-11-06 20:00 accorp Note Added: 0103920
2017-11-06 22:47 Juha Manninen Note Added: 0103930
2017-11-07 22:31 Bart Broersma Note Added: 0103948
2017-11-07 22:31 Bart Broersma Note Edited: 0103948 View Revisions
2017-11-10 00:24 Ondrej Pokorny Note Added: 0103982
2017-11-24 21:19 Juha Manninen Relationship added related to 0032196
2017-11-24 21:20 Juha Manninen Relationship deleted related to 0032196
2017-11-24 21:44 Juha Manninen Fixed in Revision r56287, r56288 => r56287, r56288, r56489
2017-11-24 21:44 Juha Manninen Note Added: 0104243
2017-11-24 21:44 Juha Manninen Status assigned => resolved
2017-11-24 21:44 Juha Manninen Resolution reopened => fixed
2018-01-09 01:07 accorp Status resolved => closed
2018-02-25 19:17 Juha Manninen Relationship added related to 0033225
2020-06-09 06:30 Jesus Reyes Relationship added related to 0037123
2020-06-30 11:30 Juha Manninen Relationship added related to 0037116