Sign in Join
Wiki copytemporary

KControls/KMemo notes: what changed

Revision 2 to 3. Removed lines have a minus sign; added lines have a plus sign.

OldNewLine
11# Introduction
22
3-This page is about the KMemo Component, a part of [[KControls]]. KMemo provides a Cross Platform (Linux, Windows, macOS) memo capable of a range of text font styles, colours and similar. Its also capable of showing images and a range of other things but that may need to be dealt with at a later stage. And, importantly, it really does work pretty much the same way across Linux, Windows and Mac. Possibly other platforms as well but I cannot speak authoritatively there.
3+This page is about the KMemo Component, a part of [[KControls]]. KMemo provides a Cross Platform (Linux, Windows, macOS) memo capable of a range of text font styles, colours and similar. Its also capable of showing images and a range of other things but that may need to be dealt with at a later stage. And, importantly, it really does work pretty much the same way across Linux, Windows and Mac. Possibly other platforms as well but I cannot speak authoritatively there.
44
5-The content presented here is in addition to the manual distributed with the KControls package. Its written by a KMemo user, not the author and could contain errors, omissions and possibly outright lies ! Someone else will undoubtedly use a different set of methods so that person is urged to document what they find too. And correct any errors they find here.
5+The content presented here is in addition to the manual distributed with the KControls package. Its written by a KMemo user, not the author and could contain errors, omissions and possibly outright lies ! Someone else will undoubtedly use a different set of methods so that person is urged to document what they find too. And correct any errors they find here.
66
77# Underlying structure
88
9-Its important, when using KMemo, to understand how the memo content and its meta data is stored. Everything is in a Block, and KMemo itself has a Blocks property. A Block has a Text property that contains the text being displayed, a Font property and a number of others controlling how the text is displayed. Every change to font style, colour, size and so on requires a new block. Block classes discussed here include TKMemoTextBlock, TKMemoParagraph, TKMemoHyperlink. A TKMemoParagraph appears between two paragraphs.
9+Its important, when using KMemo, to understand how the memo content and its meta data is stored. Everything is in a Block, and KMemo itself has a Blocks property. A Block has a Text property that contains the text being displayed, a Font property and a number of others controlling how the text is displayed. Every change to font style, colour, size and so on requires a new block. Block classes discussed here include TKMemoTextBlock, TKMemoParagraph, TKMemoHyperlink. A TKMemoParagraph appears between two paragraphs.
1010
11-This *'line of*text is an*' example.*
11+This **line of _text is an_** _example._
1212
13-It will contain 5 blocks.
13+It will contain 5 blocks.
1414
15-- Block 0 "This " - normal font.
16-- Block 1 "line of " - bold
17-- Block 2 "text is an " - bold and italic
18-- Block 3 "example." - italic
19-- Block 4 - a paragraph marker.
15+ * Block 0 "This " - normal font.
16+ * Block 1 "line of " - bold
17+ * Block 2 "text is an " - bold and italic
18+ * Block 3 "example." - italic
19+ * Block 4 - a paragraph marker.
2020
21-The KMemo, Block and Blocks classes have a large number of other Properties and Methods, far too many to possibly document here. And there are also many associated classes. Lazarus will prompt you with them and the names are reasonably intuitive.
21+The KMemo, Block and Blocks classes have a large number of other Properties and Methods, far too many to possibly document here. And there are also many associated classes. Lazarus will prompt you with them and the names are reasonably intuitive.
2222
2323# Inserting Text
2424
25-Easy. KMemo1.Blocks.AddTextBlock(AString). That will append the block at the end of any existing blocks. You can add a number after the string parameter and it will be inserted at that BlockIndex. Its a function, returning the TKMemoBlock so, once created you can alter how that text is presented.
26-
27-``` pascal
25+Easy. KMemo1.Blocks.AddTextBlock(AString). That will append the block at the end of any existing blocks. You can add a number after the string parameter and it will be inserted at that BlockIndex. Its a function, returning the TKMemoBlock so, once created you can alter how that text is presented.
26+```
2827procedure Form1.AddText();
29-var
30- TB: TKMemoTextBlock;
31-begin
32- TB := KMemo1.Blocks.AddTextBlock(InStr);
33- TB.TextStyle.Font.Size := 16;
34- TB.TextStyle.Font.Style := TB.TextStyle.Font.Style + [fsBold];
35-end;
28+ var
29+ TB: TKMemoTextBlock;
30+ begin
31+ TB := KMemo1.Blocks.AddTextBlock(InStr);
32+ TB.TextStyle.Font.Size := 16;
33+ TB.TextStyle.Font.Style := TB.TextStyle.Font.Style + [fsBold];
34+ end;
3635```
3736
38-Now, presumably, AddTextBlock() has allocated some memory for that TextBlock, we assume it will Free it when appropriate.
39-
40-If you wish to insert some text at a particular spot that will take on the font characteristics of the insertion point, its even easier. Several functions will return a SelIndex (its just a count of characters starting at zero) -
37+Now, presumably, AddTextBlock() has allocated some memory for that TextBlock, we assume it will Free it when appropriate.
4138
42-``` pascal
39+If you wish to insert some text at a particular spot that will take on the font characteristics of the insertion point, its even easier. Several functions will return a SelIndex (its just a count of characters starting at zero) -
40+```
4341KMemo1.Blocks.InsertPlainText(SelIndex, 'some bit of text to insert');
4442```
4543
4644# Selections
4745
48-... On our selection ... (sorry, Aussie joke)
46+... On our selection ... (sorry, Aussie joke)
4947
50-Kmemo offers a couple of functions, KMemo1.Blocks.RealSelStart, KMemo1.Blocks.RealSelEnd that return (read only) SelectionIndexes. ~End is always greater (or equal to) than ~Start unlike KMemo1.SelStart, Kmemo1.SelEnd which are sensitive to the direction the user wiped the mouse. KMemo1.Blocks.RealSelStart tracks the cursor position while a user is typing.
48+Kmemo offers a couple of functions, KMemo1.Blocks.RealSelStart, KMemo1.Blocks.RealSelEnd that return (read only) SelectionIndexes. ~End is always greater (or equal to) than ~Start unlike KMemo1.SelStart, Kmemo1.SelEnd which are sensitive to the direction the user wiped the mouse. KMemo1.Blocks.RealSelStart tracks the cursor position while a user is typing.
5149
52-The function KMemo1..Blocks.SelText returns the selected text (if any) irrespective of intervening block structure. Note that it might include newline characters.
50+The function KMemo1..Blocks.SelText returns the selected text (if any) irrespective of intervening block structure. Note that it might include newline characters.
5351
5452# Miscellaneous
5553
56-NOTE: By default upon creation of a table, a text block containing a new line is created within it. Usually it has to be removed by:
57-
58-``` pascal
54+NOTE: By default upon creation of a table, a text block containing a new line is created within it. Usually it has to be removed by:
55+```
5956procedure Form1.AddText();
60- KMemo1.Blocks.Clear;end;
57+ KMemo1.Blocks.Clear;end;
6158```
6259
63-**WARNING:** TKmemo does **not** support RightToLeft languages.
60+**WARNING:** TKmemo does **not** support RightToLeft languages.
6461
6562# Playing With Blocks
6663
67-You will get used to working with the two indexes, one (utf8) character based and the other Block based. Both start at zero.
64+You will get used to working with the two indexes, one (utf8) character based and the other Block based. Both start at zero.
6865
69-The Blocks class has a property, Items that can let you address an individual block, so KMemo1.Blocks.Items\[BlockNo\] is a particular block. And it has a whole bunch of properties and methods.
66+The Blocks class has a property, Items that can let you address an individual block, so KMemo1.Blocks.Items[BlockNo] is a particular block. And it has a whole bunch of properties and methods.
7067
71-There are KMemo1.Blocks.Count blocks in a KMemo. And there is length(KMemo1.Blocks.Text) characters but only in a Unix based system such as Linux or macOS. All systems allow one character for a Paragraph marker in the KMemo itself but Windows, being Windows puts a CR/LF, (#13#10) at the end of each paragraph in KMemo1.Blocks.Text. So you need allow for that, for an example, see below about Searching.
68+There are KMemo1.Blocks.Count blocks in a KMemo. And there is length(KMemo1.Blocks.Text) characters but only in a Unix based system such as Linux or macOS. All systems allow one character for a Paragraph marker in the KMemo itself but Windows, being Windows puts a CR/LF, (#13#10) at the end of each paragraph in KMemo1.Blocks.Text. So you need allow for that, for an example, see below about Searching.
7269
7370**How to convert between the two ? Like this**
74-
75-``` pascal
71+```
7672var
77- BlockNo, CharNo, LocalIndex : longint;
78-begin
79- CharNo := SomeValue;
80- BlockNo := Kmemo1.Blocks.IndexToBlockIndex(CharNo, LocalIndex);
73+ BlockNo, CharNo, LocalIndex : longint;
74+ begin
75+ CharNo := SomeValue;
76+ BlockNo := Kmemo1.Blocks.IndexToBlockIndex(CharNo, LocalIndex);
8177```
8278
83-BlockNo now has what block number CharNo points to and LocalIndex tells us how far in the block we are. Useful information, if LocalIndex is 0, we are at the start of a block, if its length(KMemo1.Blocks.Items\[BlockNo\].Text)-1 we must be at the end of a block. Dont forget that the Text component of a Block is, effectively, a Pascal string and it starts at 1, not zero. So, the first character on the first line of a KMemo is
84-
85-- KMemo.Blocks.Items\[0\].Text\[1\]
86-- TKMemoSelectionIndex = 0
87-- and Kmemo1.Blocks.IndexToBlockIndex(0, LocalIndex); // will return (block) 0 and set LocalIndex to 0
79+BlockNo now has what block number CharNo points to and LocalIndex tells us how far in the block we are. Useful information, if LocalIndex is 0, we are at the start of a block, if its length(KMemo1.Blocks.Items[BlockNo].Text)-1 we must be at the end of a block. Dont forget that the Text component of a Block is, effectively, a Pascal string and it starts at 1, not zero. So, the first character on the first line of a KMemo is
8880
89-**The reverse** -
81+ * KMemo.Blocks.Items[0].Text[1]
82+ * TKMemoSelectionIndex = 0
83+ * and Kmemo1.Blocks.IndexToBlockIndex(0, LocalIndex); // will return (block) 0 and set LocalIndex to 0
9084
91-``` pascal
92-CharNo := KMemo1.Blocks.BlockToIndex(KMemo1.Blocks.Items[BlockNo]);
85+**The reverse** \-
9386```
94-
95-will convert a blocknumber to SelectionIndex type of number. A SelectionIndex is a 0 based count of UTF8 characters, not bytes.
87+CharNo := KMemo1.Blocks.BlockToIndex(KMemo1.Blocks.Items[BlockNo]);
88+```
9689
97-When iterating over blocks, important to know what each block is -
90+will convert a blocknumber to SelectionIndex type of number. A SelectionIndex is a 0 based count of UTF8 characters, not bytes.
9891
99-``` pascal
92+When iterating over blocks, important to know what each block is -
93+```
10094if KMemo1.Blocks.Items[BlockNo].ClassNameIs('TKMemoHyperlink') then
101- URL := TKMemoHyperlink(KMemo1.Blocks.Items[BlockNo].URL);
95+ URL := TKMemoHyperlink(KMemo1.Blocks.Items[BlockNo].URL);
10296```
10397
104-Here we have found out that the Block at BlockNo is a Hyperlink block, we can therefore cast it to TKMemoHyperlink and get the URL stored there previously.
105-
106-``` pascal
98+Here we have found out that the Block at BlockNo is a Hyperlink block, we can therefore cast it to TKMemoHyperlink and get the URL stored there previously.
99+```
107100Kmemo1.Blocks.Delete(BlockNo);
108101```
109102
110-To delete a block. The source says parameter is an integer, not a TKMemoBlockIndex so you may miss it when looking for something to delete a block. But that appears to be the one to use !
111-
112-``` pascal
103+To delete a block. The source says parameter is an integer, not a TKMemoBlockIndex so you may miss it when looking for something to delete a block. But that appears to be the one to use !
104+```
113105KMemo1.Blocks.DeleteChar(Index);
114106```
115107
116-Can also be a bit confusing. You would expect it to delete the character pointed to by Index, and yes, it might. But if there is another area of Text selected when you call it, instead, it will delete that other area of text. And that can be quite a surprise ! So, something like:
117-
118-``` pascal
108+Can also be a bit confusing. You would expect it to delete the character pointed to by Index, and yes, it might. But if there is another area of Text selected when you call it, instead, it will delete that other area of text. And that can be quite a surprise ! So, something like:
109+```
119110KMemo1.Select(Index, 0);
120-while Cnt < Len do begin
121- KMemo1.Blocks.DeleteChar(Index);
122- inc(Cnt);
123-end;
111+ while Cnt < Len do begin
112+ KMemo1.Blocks.DeleteChar(Index);
113+ inc(Cnt);
114+ end;
124115```
125116
126-And, you may need to restore the selection points afterwards.
117+And, you may need to restore the selection points afterwards.
127118
128-As a paragraph may consist of many blocks and one para block, its useful to know where that para block is. KMemo1.NearestParagraphIndex returns the index of the FOLLOWING para block (obviously, not necessarily the 'nearest').
119+As a paragraph may consist of many blocks and one para block, its useful to know where that para block is. KMemo1.NearestParagraphIndex returns the index of the FOLLOWING para block (obviously, not necessarily the 'nearest').
129120
130121# Hyperlinks
131122
132-TKMemo supports text-only hyperlinks. As of October 2017 Hyperlinks containing images are not supported.
133-
134-OK, lets suppose you have a KMemo full of text and you want to make a word of it a hyperlink. It will be blue, have an underline and when clicked, do something. The hyperlink will be one block (remember, everything is a block), so if the text we want to convert is already a discrete block, easy, delete it and then insert a new hyperlink block with the same text. However, its more likely we need to split a block. The process is to delete the characters that we want to become the link, split the block at that spot, create a TKMemoHyperlink block, configure it and insert it between the split blocks.
123+TKMemo supports text-only hyperlinks. As of October 2017 Hyperlinks containing images are not supported.
135124
136-This procedure will make a hyperlink at the character position, Index and it will be Len long. It exits doing nothing if that spot is already a hyperlink.
125+OK, lets suppose you have a KMemo full of text and you want to make a word of it a hyperlink. It will be blue, have an underline and when clicked, do something. The hyperlink will be one block (remember, everything is a block), so if the text we want to convert is already a discrete block, easy, delete it and then insert a new hyperlink block with the same text. However, its more likely we need to split a block. The process is to delete the characters that we want to become the link, split the block at that spot, create a TKMemoHyperlink block, configure it and insert it between the split blocks.
137126
138-``` pascal
127+This procedure will make a hyperlink at the character position, Index and it will be Len long. It exits doing nothing if that spot is already a hyperlink.
128+```
139129procedure TForm1.MakeDemoLink(Index, Len : longint);
140-var
141- Hyperlink: TKMemoHyperlink;
142- BlockNo, Offset : longint;
143- DontSplit : Boolean = false;
144- Link : ANSIString;
145-begin
146- // Is it already a Hyperlink ?
147- BlockNo := KMemo1.Blocks.IndexToBlockIndex(Index, Offset);
148- if KMemo1.Blocks.Items[BlockNo].ClassNameIs('TKHyperlink') then exit();
149- Link := copy(KMemo1.Blocks.Items[BlockNo].Text, Offset+1, Len);
150- if length(Kmemo1.Blocks.Items[BlockNo].Text) = Len then DontSplit := True;
151- TheKMemo.SelStart := Index;
152- TheKMemo.SelLength := Len;
153- TheKMemo.ClearSelection();
154- if not DontSplit then BlockNo := KMemo1.SplitAt(Index);
155- Hyperlink := TKMemoHyperlink.Create;
156- Hyperlink.Text := Link;
157- Hyperlink.OnClick := @OnUserClickLink;
158- KMemo1.Blocks.AddHyperlink(Hyperlink, BlockNo);
159-end;
130+ var
131+ Hyperlink: TKMemoHyperlink;
132+ BlockNo, Offset : longint;
133+ DontSplit : Boolean = false;
134+ Link : ANSIString;
135+ begin
136+ // Is it already a Hyperlink ?
137+ BlockNo := KMemo1.Blocks.IndexToBlockIndex(Index, Offset);
138+ if KMemo1.Blocks.Items[BlockNo].ClassNameIs('TKHyperlink') then exit();
139+ Link := copy(KMemo1.Blocks.Items[BlockNo].Text, Offset+1, Len);
140+ if length(Kmemo1.Blocks.Items[BlockNo].Text) = Len then DontSplit := True;
141+ TheKMemo.SelStart := Index;
142+ TheKMemo.SelLength := Len;
143+ TheKMemo.ClearSelection();
144+ if not DontSplit then BlockNo := KMemo1.SplitAt(Index);
145+ Hyperlink := TKMemoHyperlink.Create;
146+ Hyperlink.Text := Link;
147+ Hyperlink.OnClick := @OnUserClickLink;
148+ KMemo1.Blocks.AddHyperlink(Hyperlink, BlockNo);
149+ end;
160150```
161151
162-Now, danger Will Robertson ! This function will fail if you pass it parameters of text that are not within one block. But only because of the "Link := copy(..." line. Easy to fix with a few extra lines of code.
163-
164-And you may need to first record the existing selection and restore it afterwards too.
152+Now, danger Will Robertson ! This function will fail if you pass it parameters of text that are not within one block. But only because of the "Link := copy(..." line. Easy to fix with a few extra lines of code.
165153
166-Oh, and whats this "@OnUserClickLink" ? Its the address of a procedure "OnUserClickLink()" that might be defined like this -
154+And you may need to first record the existing selection and restore it afterwards too.
167155
168-``` pascal
156+Oh, and whats this "@OnUserClickLink" ? Its the address of a procedure "OnUserClickLink()" that might be defined like this -
157+```
169158procedure TEditBoxForm.OnUserClickLink(sender : TObject);
170-begin
171- showmessage('Wow, someone clicked ' + TKMemoHyperlink(Sender).Text);
172-end;
159+ begin
160+ showmessage('Wow, someone clicked ' + TKMemoHyperlink(Sender).Text);
161+ end;
173162```
174163
175164# Search
176165
177-Despite all those methods, I could not find a Search function. But if your application is just text based (ie no images or nested containers) its not that difficult to search KMemo1.Blocks.Text, treat it as an ANSIString. On Unix like systems, the position in KMemo1.Blocks.Text can be used directly as a TKMemoSelectionIndex. Sadly, in Windows, KMemo1.Blocks.Text has Window's two character line endings, but in KMemo itself, only one char is reserved for line endings. So, count the \#13 characters from the start to the position of interest and subtract that from what becomes our TKMemoSelection index.
178-
179-This UTF8 compliant function will return 0 or the TKMemoSelectionIndex of the searched for term. It depends on the LazUTF8 unit so add it to your uses clause. It can be called repeatably, passing the previously found index plus 1 each time to find multiple incidences of a term. Set MoveCursor to True and it will show the user where the searched for term is. You will note most of the code is about fixing up the Windows problem.
166+Despite all those methods, I could not find a Search function. But if your application is just text based (ie no images or nested containers) its not that difficult to search KMemo1.Blocks.Text, treat it as an ANSIString. On Unix like systems, the position in KMemo1.Blocks.Text can be used directly as a TKMemoSelectionIndex. Sadly, in Windows, KMemo1.Blocks.Text has Window's two character line endings, but in KMemo itself, only one char is reserved for line endings. So, count the #13 characters from the start to the position of interest and subtract that from what becomes our TKMemoSelection index.
180167
181-``` pascal
168+This UTF8 compliant function will return 0 or the TKMemoSelectionIndex of the searched for term. It depends on the LazUTF8 unit so add it to your uses clause. It can be called repeatably, passing the previously found index plus 1 each time to find multiple incidences of a term. Set MoveCursor to True and it will show the user where the searched for term is. You will note most of the code is about fixing up the Windows problem.
169+```
182170function TForm1.Search(Term : ANSIString; StartAt : longint = 1; MoveCursor : Boolean = False) : longint;
183-var
184- Ptr, EndP : pchar;
185- Offset : longint;
186- NumbCR : longint;
187-begin
188- Result := UTF8Pos(Term, KMemo1.Blocks.text, StartAt);
189- {$IFDEF WINDOWS} // Sadley we need to subtract the extra CR windows adds to a newline
190- if Result = 0 then exit();
191- NumbCR := 0;
192- Ptr := PChar(KMemo1.Blocks.text);
193- EndP := Ptr + Result-1;
194- while Ptr < EndP do begin
195- if Ptr^ = #13 then inc(NumbCR);
196- inc(Ptr);
197- end;
198- Result := Result - NumbCR;
199- {$ENDIF} // does no harm in Unix but adds 66mS with my test note.
200- if MoveCursor then begin
201- KMemo1.SelStart := Result;
202- KMemo1.SelEnd := Result;
203- KMemo1.SetFocus;
171+ var
172+ Ptr, EndP : pchar;
173+ Offset : longint;
174+ NumbCR : longint;
175+ begin
176+ Result := UTF8Pos(Term, KMemo1.Blocks.text, StartAt);
177+ {$IFDEF WINDOWS} // Sadley we need to subtract the extra CR windows adds to a newline
178+ if Result = 0 then exit();
179+ NumbCR := 0;
180+ Ptr := PChar(KMemo1.Blocks.text);
181+ EndP := Ptr + Result-1;
182+ while Ptr < EndP do begin
183+ if Ptr^ = #13 then inc(NumbCR);
184+ inc(Ptr);
185+ end;
186+ Result := Result - NumbCR;
187+ {$ENDIF} // does no harm in Unix but adds 66mS with my test note.
188+ if MoveCursor then begin
189+ KMemo1.SelStart := Result;
190+ KMemo1.SelEnd := Result;
191+ KMemo1.SetFocus;
192+ end;
204193 end;
205-end;
206194```
207195
208196# Bullets
209197
210-Bullets are a popular way to display a series of (maybe) short items. KMemo does have an "out of the box" bullet capability but not every part of it works exactly as people generally expect. The underlying issue is that KMemo records the "bulletness" of a bit of text in the paragraph marker at the end of the text. This results in some unexpected (to the end user) behaviour, particularly when using backspace key at beginning or end of a line of bullet text -
211-
212-- If the user backspaces from immediately after a bullet, perhaps hoping to delete the last character there, instead, they will delete the paragraph marker (not surprising) and remove the bullet attribute from that line of text (thats is surprising to end user).
213-- If the user places the cursor at start of a bullet text and presses backspace, expecting to cancel the "bulletness", instead, it acts as a backspace should and removes the leading paragraph marked and merges the line with the line above. And if the line above was not already a bullet, it now becomes one !
198+Bullets are a popular way to display a series of (maybe) short items. KMemo does have an "out of the box" bullet capability but not every part of it works exactly as people generally expect. The underlying issue is that KMemo records the "bulletness" of a bit of text in the paragraph marker at the end of the text. This results in some unexpected (to the end user) behaviour, particularly when using backspace key at beginning or end of a line of bullet text -
214199
215-There are a number of subcases of the above. Each has to be dealt with. The solution at the moment is to intercept the backspace key and, if the cursor is in one of the critical positions with respect to a bullet, prevent it being passed on and doing what is expected there and then. This works but but is complicated.
200+ * If the user backspaces from immediately after a bullet, perhaps hoping to delete the last character there, instead, they will delete the paragraph marker (not surprising) and remove the bullet attribute from that line of text (thats is surprising to end user).
201+ * If the user places the cursor at start of a bullet text and presses backspace, expecting to cancel the "bulletness", instead, it acts as a backspace should and removes the leading paragraph marked and merges the line with the line above. And if the line above was not already a bullet, it now becomes one !
216202
217-Firstly, in KMemo1KeyDown() we look at every keystroke, we have to anyway, will need to intercept any one of the ctrl or similar key strokes for other reasons. But we are very interested in a Backspace -
203+There are a number of subcases of the above. Each has to be dealt with. The solution at the moment is to intercept the backspace key and, if the cursor is in one of the critical positions with respect to a bullet, prevent it being passed on and doing what is expected there and then. This works but but is complicated.
218204
219-``` pascal
205+Firstly, in KMemo1KeyDown() we look at every keystroke, we have to anyway, will need to intercept any one of the ctrl or similar key strokes for other reasons. But we are very interested in a Backspace -
206+```
220207procedure TEditBoxForm.KMemo1KeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
221-begin
222- // do all sorts of stuff and exit early if not a Backspace
223- if BackSpaceBullet() then
224- Key := 0; // eat that key because it was used in BackSpaceBullet()
225- // if BackspaceBullet() ret false, let the Backspace go through to the KMemo
226-end;
208+ begin
209+ // do all sorts of stuff and exit early if not a Backspace
210+ if BackSpaceBullet() then
211+ Key := 0; // eat that key because it was used in BackSpaceBullet()
212+ // if BackspaceBullet() ret false, let the Backspace go through to the KMemo
213+ end;
227214```
228215
229-OK, now BackspaceBullet() will check to see if the cursor is in a position near a bullet that needs our attention.
230-
231-``` pascal
216+OK, now BackspaceBullet() will check to see if the cursor is in a position near a bullet that needs our attention.
217+```
232218function TEditBoxForm.BackSpaceBullet() : boolean;
233-var
234- BlockNo, PosInBlock : longint;
235- ParaBlockNo : integer = -1;
236- IsBulletPara : boolean = false; // The para the cursor is in.
237-begin
238- Result := false;
239- ParaBlockNo := Kmemo1.NearestParagraphIndex; // Thats para blockno. that controls where cursor is.
240- if ParaBlockNo < 2 then exit; // Don't mess with title.
241- BlockNo := kmemo1.Blocks.IndexToBlockIndex(KMemo1.RealSelStart, PosInBlock); // Block with cursor
242- if PosInBlock > 0 then exit; // not the sort of BS we are looking for.
243- // ======== BS when on first char of bullet line.========
244- if TKMemoParagraph(KMemo1.Blocks[ParaBlockNo]).Numbering <> pnuNone then begin // ie, this is a bullet para.
245- IsBulletPara := True;
246- if kmemo1.Blocks.Items[BlockNo-1].ClassNameIs('TkMemoParagraph') then begin // Cursor is on first char of a bullet para.
247- SetBullet(TKMemoParagraph(KMemo1.Blocks[ParaBlockNo]), False);
248- exit(True); // My work here is done.
219+ var
220+ BlockNo, PosInBlock : longint;
221+ ParaBlockNo : integer = -1;
222+ IsBulletPara : boolean = false; // The para the cursor is in.
223+ begin
224+ Result := false;
225+ ParaBlockNo := Kmemo1.NearestParagraphIndex; // Thats para blockno. that controls where cursor is.
226+ if ParaBlockNo < 2 then exit; // Don't mess with title.
227+ BlockNo := kmemo1.Blocks.IndexToBlockIndex(KMemo1.RealSelStart, PosInBlock); // Block with cursor
228+ if PosInBlock > 0 then exit; // not the sort of BS we are looking for.
229+ // ======== BS when on first char of bullet line.========
230+ if TKMemoParagraph(KMemo1.Blocks[ParaBlockNo]).Numbering <> pnuNone then begin // ie, this is a bullet para.
231+ IsBulletPara := True;
232+ if kmemo1.Blocks.Items[BlockNo-1].ClassNameIs('TkMemoParagraph') then begin // Cursor is on first char of a bullet para.
233+ SetBullet(TKMemoParagraph(KMemo1.Blocks[ParaBlockNo]), False);
234+ exit(True); // My work here is done.
235+ end;
249236 end;
250- end;
251- // If previous para is already a bullet and current one is not, we set current one to same
252- // bullet level as previous and allow BS to do the rest. BlockNo would have been set above
253- // and we know we are on the first char of the paragraph.
254- if (BlockNo > 0) and (not IsBulletPara) then begin // Cursor is on first char of a non-bullet para.
255- if kmemo1.Blocks.Items[BlockNo-1].ClassNameIs('TkMemoParagraph') // previous block is a para ......
256- and (TKMemoParagraph(KMemo1.Blocks[BlockNo-1]).Numbering <> pnuNone) then begin // and previous block is bullet !
257- SetBullet(TKMemoParagraph(KMemo1.Blocks[ParaBlockNo]), True, TKMemoParagraph(KMemo1.Blocks[BlockNo-1]).Numbering);
258- exit(False); // lets BS go through to KMemo.
259- end;
237+ // If previous para is already a bullet and current one is not, we set current one to same
238+ // bullet level as previous and allow BS to do the rest. BlockNo would have been set above
239+ // and we know we are on the first char of the paragraph.
240+ if (BlockNo > 0) and (not IsBulletPara) then begin // Cursor is on first char of a non-bullet para.
241+ if kmemo1.Blocks.Items[BlockNo-1].ClassNameIs('TkMemoParagraph') // previous block is a para ......
242+ and (TKMemoParagraph(KMemo1.Blocks[BlockNo-1]).Numbering <> pnuNone) then begin // and previous block is bullet !
243+ SetBullet(TKMemoParagraph(KMemo1.Blocks[ParaBlockNo]), True, TKMemoParagraph(KMemo1.Blocks[BlockNo-1]).Numbering);
244+ exit(False); // lets BS go through to KMemo.
245+ end;
246+ end;
260247 end;
261-end;
262248```
263249
264250If it decides it needs to act, it will what is necessary and return True, indicating to the calling process that it should not allow the Backspace keystroke to be passed on to the KMemo. If the cursor is not in a position that should affect existing bullets, it returns false, the calling procedure will allow the Backspace to go to the KMemo which will use it as expected. The SetBullet() method will increase of decrease the Bullet Level. Further details can be seen in <https://github.com/tomboy-notes/tomboy-ng/blob/master/source/editbox.pas>
265251
266252# Font names, styles, and sizes
267253
268-KMemo supports many of the same font style options as [TFont](https://support.smartbear.com/testcomplete/docs/reference/user-forms/helper-objects/tfont/style.html), its Delphi cousin. To set styles:
269-
270-``` pascal
254+KMemo supports many of the same font style options as [TFont](<https://support.smartbear.com/testcomplete/docs/reference/user-forms/helper-objects/tfont/style.html>), its Delphi cousin. To set styles:
255+```
271256Block.TextStyle.Font.Name := 'monospace'; // make font fixed width (untested on macOS and Windows)
272-Block.TextStyle.Font.Name := 'Arial'; // set font to Arial
273-Block.TextStyle.Font.Style := Block.TextStyle.Font.Style + [fsBold]; // embolden it!
274-Block.TextStyle.Font.Style := Block.TextStyle.Font.Style + [fsItalic]; // italicize
275-Block.TextStyle.Font.Style := Block.TextStyle.Font.Style + [fsStrikeout]; // strikeout
276-Block.TextStyle.Font.Style := Block.TextStyle.Font.Style + [fsUnderline]; // underline
257+ Block.TextStyle.Font.Name := 'Arial'; // set font to Arial
258+ Block.TextStyle.Font.Style := Block.TextStyle.Font.Style + [fsBold]; // embolden it!
259+ Block.TextStyle.Font.Style := Block.TextStyle.Font.Style + [fsItalic]; // italicize
260+ Block.TextStyle.Font.Style := Block.TextStyle.Font.Style + [fsStrikeout]; // strikeout
261+ Block.TextStyle.Font.Style := Block.TextStyle.Font.Style + [fsUnderline]; // underline
277262```
278263
279-You can use similar syntax for font size, color, subscript, and superscript.
280-
281-``` pascal
264+You can use similar syntax for font size, color, subscript, and superscript.
265+```
282266Block.TextStyle.Font.Size := 16; // set font size to 16
283-Block.TextStyle.Font.Color := clRed // use the TColor value of any color
284-Block.TextStyle.ScriptPosition := tpoSubscript; // subscript
285-Block.TextStyle.ScriptPosition := tpoSuperscript; // superscript
267+ Block.TextStyle.Font.Color := clRed // use the TColor value of any color
268+ Block.TextStyle.ScriptPosition := tpoSubscript; // subscript
269+ Block.TextStyle.ScriptPosition := tpoSuperscript; // superscript
286270```
287271
288-(The snippets above don't exhaustively cover all font style options provided with KMemo)
272+(The snippets above don't exhaustively cover all font style options provided with KMemo)
289273
290274# Copy on Selection and Middle Button Paste
291275
292-Unix (ie Linux but not Mac) users have long been used to an alternative copy and paste model. Any text selected is automatically made available to current and other applications with a press of the middle mouse button (or, typically, three finger tap on the touch pad). Most LCL components already do this on Linux but KMemo does not. Its easily implemented by using a capability built into the clipbrd unit.
293-
294-When an app detects you have selected some text, it registers the address of a function that knows how to copy that text to where ever it happens to be required. Unlike the 'other' clipboard, the text is not copied until needed. You will use the PrimarySelection TClipboard object already declared in clipbrd unit and a couple of small procedures -
276+Unix (ie Linux but not Mac) users have long been used to an alternative copy and paste model. Any text selected is automatically made available to current and other applications with a press of the middle mouse button (or, typically, three finger tap on the touch pad). Most LCL components already do this on Linux but KMemo does not. Its easily implemented by using a capability built into the clipbrd unit.
295277
296-``` pascal
278+When an app detects you have selected some text, it registers the address of a function that knows how to copy that text to where ever it happens to be required. Unlike the 'other' clipboard, the text is not copied until needed. You will use the PrimarySelection TClipboard object already declared in clipbrd unit and a couple of small procedures -
279+```
297280uses clipbrd;
298-....
299-
300-procedure TForm1.KMemo1MouseUp(Sender: TObject; Button: TMouseButton;
301- Shift: TShiftState; X, Y: Integer);
302-var
303- Point : TPoint;
304- LinePos : TKmemoLinePosition;
305-begin
306- if Button = mbMiddle then begin
307- Point := TPoint.Create(X, Y);
308- PrimaryPaste(KMemo1.PointToIndex(Point, true, true, LinePos));
309- exit();
281+ ....
282+
283+ procedure TForm1.KMemo1MouseUp(Sender: TObject; Button: TMouseButton;
284+ Shift: TShiftState; X, Y: Integer);
285+ var
286+ Point : TPoint;
287+ LinePos : TKmemoLinePosition;
288+ begin
289+ if Button = mbMiddle then begin
290+ Point := TPoint.Create(X, Y);
291+ PrimaryPaste(KMemo1.PointToIndex(Point, true, true, LinePos));
292+ exit();
293+ end;
294+ if KMemo1.SelAvail and
295+ (Kmemo1.Blocks.SelLength <> 0) then
296+ SetPrimarySelection()
297+ else
298+ UnsetPrimarySelection();
310299 end;
311- if KMemo1.SelAvail and
312- (Kmemo1.Blocks.SelLength <> 0) then
313- SetPrimarySelection()
314- else
315- UnsetPrimarySelection();
316-end;
317-
318-procedure TForm1.SetPrimarySelection;
319-var
320- FormatList: Array [0..1] of TClipboardFormat;
321-begin
322- if (PrimarySelection.OnRequest=@PrimaryCopy) then exit;
323- FormatList[0] := CF_TEXT;
324- try
325- PrimarySelection.SetSupportedFormats(1, @FormatList[0]);
326- PrimarySelection.OnRequest:=@PrimaryCopy;
327- except
328- end;
329-end;
330-
331-procedure TForm1.UnsetPrimarySelection;
332-begin
333- if PrimarySelection.OnRequest=@PrimaryCopy then
334- PrimarySelection.OnRequest:=nil;
335-end;
300+
301+ procedure TForm1.SetPrimarySelection;
302+ var
303+ FormatList: Array [0..1] of TClipboardFormat;
304+ begin
305+ if (PrimarySelection.OnRequest=@PrimaryCopy) then exit;
306+ FormatList[0] := CF_TEXT;
307+ try
308+ PrimarySelection.SetSupportedFormats(1, @FormatList[0]);
309+ PrimarySelection.OnRequest:=@PrimaryCopy;
310+ except
311+ end;
312+ end;
336313
337-procedure TForm1.PrimaryCopy(
338- const RequestedFormatID: TClipboardFormat; Data: TStream);
339-var
340- s : string;
341-begin
342- S := KMemo1.Blocks.SelText;
343- if RequestedFormatID = CF_TEXT then
344- if length(S) > 0 then
345- Data.Write(s[1],length(s));
346-end;
347-
348-procedure TForm1.PrimaryPaste(SelIndex : integer);
349-var
350- Buff : string;
351-begin
352- if PrimarySelection.HasFormat(CF_TEXT) then begin // I don't know if this is useful at all ?
353- Buff := PrimarySelection().AsText;
354- if Buff <> '' then
355- KMemo1.Blocks.InsertPlainText(SelIndex, Buff);
356- end;
357- // PrimarySelection is a function that returns a TClipboard object.
358- // so, here, we call TClipboard.astext, but not the 'normal' clipboard.
359-end;
360-
361-procedure TForm1.FormDestroy(Sender: TObject);
362-begin
363- UnsetPrimarySelection;
364-end;
314+ procedure TForm1.UnsetPrimarySelection;
315+ begin
316+ if PrimarySelection.OnRequest=@PrimaryCopy then
317+ PrimarySelection.OnRequest:=nil;
318+ end;
319+
320+ procedure TForm1.PrimaryCopy(
321+ const RequestedFormatID: TClipboardFormat; Data: TStream);
322+ var
323+ s : string;
324+ begin
325+ S := KMemo1.Blocks.SelText;
326+ if RequestedFormatID = CF_TEXT then
327+ if length(S) > 0 then
328+ Data.Write(s[1],length(s));
329+ end;
330+
331+ procedure TForm1.PrimaryPaste(SelIndex : integer);
332+ var
333+ Buff : string;
334+ begin
335+ if PrimarySelection.HasFormat(CF_TEXT) then begin // I don't know if this is useful at all ?
336+ Buff := PrimarySelection().AsText;
337+ if Buff <> '' then
338+ KMemo1.Blocks.InsertPlainText(SelIndex, Buff);
339+ end;
340+ // PrimarySelection is a function that returns a TClipboard object.
341+ // so, here, we call TClipboard.astext, but not the 'normal' clipboard.
342+ end;
343+
344+ procedure TForm1.FormDestroy(Sender: TObject);
345+ begin
346+ UnsetPrimarySelection;
347+ end;
365348```
366349
367-Note we copy the address of PrimaryCopy() into the PrimarySelection system. As other applications may well be calling this function, you must ensure the function parameter list remains as presented. Primary paste on the other hand is up to you. It needs to know how to paste an arbitrary bit of text into your application.
350+Note we copy the address of PrimaryCopy() into the PrimarySelection system. As other applications may well be calling this function, you must ensure the function parameter list remains as presented. Primary paste on the other hand is up to you. It needs to know how to paste an arbitrary bit of text into your application.
368351
369-The Lazarus IDE does more than just plain text, see synedit.pp and search for PrimarySelection - thanks to Martin_fr for the pointer.
352+The Lazarus IDE does more than just plain text, see synedit.pp and search for PrimarySelection - thanks to Martin_fr for the pointer.
370353
371-Further research is indicated -
354+Further research is indicated -
372355
373-- Should we {\$ifdef LINUX} it so it does no harm in Mac and Windows. Lazarus allows windows users to do this within the app. Mac users have no concept of a middle mouse button so can never use it. Test.
374-- Need to do more through tests with UTF8.
375-- Is the CF_TEXT test in PrimaryPaste useful ? Is there a better way to test if content is available ?
356+ * Should we {$ifdef LINUX} it so it does no harm in Mac and Windows. Lazarus allows windows users to do this within the app. Mac users have no concept of a middle mouse button so can never use it. Test.
357+ * Need to do more through tests with UTF8.
358+ * Is the CF_TEXT test in PrimaryPaste useful ? Is there a better way to test if content is available ?
376359
377360# Lines
378361
379-You can refer to individual lines of text within the KMemo using KMemo1.Blocks.Lines.Items\[I\] which returns a TKMemoLine. It has useful data in it about the line including start and end blocks and indexes. Note in this case, 'Line' refers to a line as currently displayed in KMemo. Just to be clear, that means, for example, altering the width of the KMemo moves the text wrapping point and alters the line numbering.
380-
381-You can get the (0 based) line number the cursor is currently on or the text that line contains -
362+You can refer to individual lines of text within the KMemo using KMemo1.Blocks.Lines.Items[I] which returns a TKMemoLine. It has useful data in it about the line including start and end blocks and indexes. Note in this case, 'Line' refers to a line as currently displayed in KMemo. Just to be clear, that means, for example, altering the width of the KMemo moves the text wrapping point and alters the line numbering.
382363
383-``` pascal
384- LineNo := Kmemo1.Blocks.IndexToLineIndex(KMemo1.Blocks.RealSelStart);
385- debugln('Current Line number ' + inttostr(LineNo));
386- debugln('[' + Kmemo1.Blocks.LineText[LineNo] + ']');
364+You can get the (0 based) line number the cursor is currently on or the text that line contains -
387365```
366+LineNo := Kmemo1.Blocks.IndexToLineIndex(KMemo1.Blocks.RealSelStart);
367+ debugln('Current Line number ' + inttostr(LineNo));
368+ debugln('[' + Kmemo1.Blocks.LineText[LineNo] + ']');
369+```
388370
389371# Locking
390372
391-As your app grows, you may find that some processes start to take a significant amount of time. Here, Locking is not refering to where we lock a process to avoid interruptions, it actually speeds up the process, and quite substantially. If you have a series of (write) operations to perform on the contents of a KMemo, apply the lock before you start, release it when finished. This sort of thing -
392-
393-``` pascal
373+As your app grows, you may find that some processes start to take a significant amount of time. Here, Locking is not refering to where we lock a process to avoid interruptions, it actually speeds up the process, and quite substantially. If you have a series of (write) operations to perform on the contents of a KMemo, apply the lock before you start, release it when finished. This sort of thing -
374+```
394375try
395- KMemo1.Blocks.LockUpdate;
396- while BlockNo < Kmemo1.Blocks.Count do begin
397- SomeCrazyProcedure(KMemo.Blocks.Items[BlockNo]);
398- inc(BlockNo);
399- end
400-finally
401- KMemo1.Blocks.UnlockUpdate;
402-end;
376+ KMemo1.Blocks.LockUpdate;
377+ while BlockNo < Kmemo1.Blocks.Count do begin
378+ SomeCrazyProcedure(KMemo.Blocks.Items[BlockNo]);
379+ inc(BlockNo);
380+ end
381+ finally
382+ KMemo1.Blocks.UnlockUpdate;
383+ end;
403384```
404385
405-Locking does not seem to make much difference where you are not writing to the contents nor for one-off procedures. It makes a big difference when you are doing a lot of stuff all at once.
406-
407-- WARNING: Do not use *KMemo1.LockUpdate* (without *.Blocks.*)!
408-- WARNING: If your application starts behaving oddly (selecting text happens slowly or does not happen at all) you have most likely missed to do *KMemo1.Blocks.UnlockUpdate;* Makes sense to use a try..finally to ensure unlock happens.
409-- WARNING: Do not attempt to move the cursor while locked. Bad things happen ....
410-
411-- **WARNING**: LockUpdate uses an internal counter (type int32), to prevent undesired unlocking by nested routines. So in order to unlock something you need to call .UnlockUpdate as many times as you have called .LockUpdate.
386+Locking does not seem to make much difference where you are not writing to the contents nor for one-off procedures. It makes a big difference when you are doing a lot of stuff all at once.
412387
413-Example:
388+ * WARNING: Do not use _KMemo1.LockUpdate_ (without _.Blocks._)!
389+ * WARNING: If your application starts behaving oddly (selecting text happens slowly or does not happen at all) you have most likely missed to do _KMemo1.Blocks.UnlockUpdate;_ Makes sense to use a try..finally to ensure unlock happens.
390+ * WARNING: Do not attempt to move the cursor while locked. Bad things happen ....
414391
415-``` pascal
416- KMemo1.Blocks.LockUpdate;
417- KMemo1.Blocks.LockUpdate;
418- KMemo1.Blocks.LockUpdate;
392+ * **WARNING** : LockUpdate uses an internal counter (type int32), to prevent undesired unlocking by nested routines. So in order to unlock something you need to call .UnlockUpdate as many times as you have called .LockUpdate.
419393
420- KMemo1.Blocks.UnLockUpdate; // Kmemo1 is still locked
421- KMemo1.Blocks.UnlockUpdate; // Kmemo1 is still locked
422- KMemo1.Blocks.UnLockUpdate; // Kmemo1 is unlocked
394+Example:
423395```
424-
425-In order to make sure that something is unlocked you could:
396+KMemo1.Blocks.LockUpdate;
397+ KMemo1.Blocks.LockUpdate;
398+ KMemo1.Blocks.LockUpdate;
399+
400+ KMemo1.Blocks.UnLockUpdate; // Kmemo1 is still locked
401+ KMemo1.Blocks.UnlockUpdate; // Kmemo1 is still locked
402+ KMemo1.Blocks.UnLockUpdate; // Kmemo1 is unlocked
403+```
426404
427-``` pascal
428- for i:=0 to 2147483647 do // or some counter with lower value to prevent running the loop forever
429- if KMemo1.Blocks.UpdateUnlocked=False
430- then KMemo1.Blocks.UnLockUpdate
431- else break;
432- if KMemo1.Blocks.UnLockUpdate=False then {error handling or whatever}
405+In order to make sure that something is unlocked you could:
433406```
407+for i:=0 to 2147483647 do // or some counter with lower value to prevent running the loop forever
408+ if KMemo1.Blocks.UpdateUnlocked=False
409+ then KMemo1.Blocks.UnLockUpdate
410+ else break;
411+ if KMemo1.Blocks.UnLockUpdate=False then {error handling or whatever}
412+```
434413
435414# Bugs
436415
437-Yep, it has some bugs and some not yet implemented features. But not many ! Please report bugs at the link below.
416+Yep, it has some bugs and some not yet implemented features. But not many ! Please report bugs at the link below.
438417
439418**Canceling Bullets**
440419
441-Late 2017 its been noted that there is a problem canceling Bullets on linux. Its fixed in the 2017-09-24 github version.
420+Late 2017 its been noted that there is a problem canceling Bullets on linux. Its fixed in the 2017-09-24 github version.
442421
443422**Direction of Selection**
444423
445-You select text (with a mouse) either from left to right or from right to left. You may notice that in KMemo, this direction is important if you use KMemo1.SelStart etc. This is **NOT** a bug, in fact, if it matters, you should be using KMemo1.RealSelStart (etc) instead.
424+You select text (with a mouse) either from left to right or from right to left. You may notice that in KMemo, this direction is important if you use KMemo1.SelStart etc. This is **NOT** a bug, in fact, if it matters, you should be using KMemo1.RealSelStart (etc) instead.
446425
447426## Copy and Paste Bug
448427
449-Kmemo, as of April, 2018 and previous has a Unicode bug that affects Linux and Mac. It shows up if you copy unicode characters between KMemos but not, for example if you copy and paste the same text via an intermediate (non KMemo) app. The reason is that when copying directly between KMemos the text is copied in RTF format (to retain any formatting). A similar and directly related issue may occur when reading or writing RTF that contains UNicode text or Bullet Points, noting the Bullet Point is, itself, a Unicode character.
428+Kmemo, as of April, 2018 and previous has a Unicode bug that affects Linux and Mac. It shows up if you copy unicode characters between KMemos but not, for example if you copy and paste the same text via an intermediate (non KMemo) app. The reason is that when copying directly between KMemos the text is copied in RTF format (to retain any formatting). A similar and directly related issue may occur when reading or writing RTF that contains UNicode text or Bullet Points, noting the Bullet Point is, itself, a Unicode character.
450429
451-In fact this bug actually consists of two unrelated bugs, one affecting the copying (or writeRTF) behaviour, the other affecting pasting (or loadRTF).
430+In fact this bug actually consists of two unrelated bugs, one affecting the copying (or writeRTF) behaviour, the other affecting pasting (or loadRTF).
452431
453432## The Copy (or WriteRTF) Problem
454433
455-On windows, the call to StringToAnsiString(C, FCodePage); at kmemortf#4098 converts a single Unicode multibyte character (in C) to single byte (code page aware ?). For example the english pound becomes \#163. On linux, it is not changed, that is, it remains a two byte unicode character. That is because in LConvEncoding#7392, the function ConvertEncoding(const s, FromEncoding, ToEncoding: string ): string; has decided the string does not need to be converted. Effectivly, as GetDefautTextEncoding() returns 'utf8'as we are asking it to convert UTF8 to UTF8. So, quite resonably, It decides to pass the string back as it is, still in multibyte form.
456-
457-However in kmemortf#4067 there is a procedure TKMemoRTFWriter.WriteUnicodeString(const AText: TKString) that gets the string 'converted' above. At line#4099 there is -
434+On windows, the call to StringToAnsiString(C, FCodePage); at kmemortf#4098 converts a single Unicode multibyte character (in C) to single byte (code page aware ?). For example the english pound becomes #163. On linux, it is not changed, that is, it remains a two byte unicode character. That is because in LConvEncoding#7392, the function ConvertEncoding(const s, FromEncoding, ToEncoding: string ): string; has decided the string does not need to be converted. Effectivly, as GetDefautTextEncoding() returns 'utf8'as we are asking it to convert UTF8 to UTF8. So, quite resonably, It decides to pass the string back as it is, still in multibyte form.
458435
459-``` pascal
460- if (Ansi <> '') and (Ansi <> #0) then
461- begin
462- S := AnsiString(Format('%s\''%.2x', [S, Ord(Ansi[1])]));
463- WasAnsi := True;
464- end;
436+However in kmemortf#4067 there is a procedure TKMemoRTFWriter.WriteUnicodeString(const AText: TKString) that gets the string 'converted' above. At line#4099 there is -
465437```
466-
467-This code assumes all char in S are single byte but as discussed, on a Linux/Mac, being UTF8 ANSI codepage system, that is not the case.
438+if (Ansi <> '') and (Ansi <> #0) then
439+ begin
440+ S := AnsiString(Format('%s\''%.2x', [S, Ord(Ansi[1])]));
441+ WasAnsi := True;
442+ end;
443+```
468444
469-Interestingly, in the next few lines of WriteUnicodeString() is code obviously intended to deal with multibyte characters. But it does not get used because line#4099 has already stepped in.
445+This code assumes all char in S are single byte but as discussed, on a Linux/Mac, being UTF8 ANSI codepage system, that is not the case.
470446
471-So, replace the test line \#4099, the block becomes -
447+Interestingly, in the next few lines of WriteUnicodeString() is code obviously intended to deal with multibyte characters. But it does not get used because line#4099 has already stepped in.
472448
473-``` pascal
474- if (length(Ansi) = 1) and (Ansi <> #0) then
475- begin
476- S := AnsiString(Format('%s\''%.2x', [S, Ord(Ansi[1])]));
477- WasAnsi := True;
478- end;
449+So, replace the test line #4099, the block becomes -
479450```
451+if (length(Ansi) = 1) and (Ansi <> #0) then
452+ begin
453+ S := AnsiString(Format('%s\''%.2x', [S, Ord(Ansi[1])]));
454+ WasAnsi := True;
455+ end;
456+```
480457
481-This will ensure that a multibyte char is passed on to next part of the procedure as was probably intended.
458+This will ensure that a multibyte char is passed on to next part of the procedure as was probably intended.
482459
483460## Paste (or LoadRTF) Issue
484461
485-KMemortf ignores "listtext" as an RTF tag (kmemortf#2381). It sets FActiveState.Group to RGUNKNOWN so other methods know not to do anything with it. However, it is still processed to some extent. In particular, if the listtext params include a unicode character, ReadSpecialCharacter() will set FIgnoreChars to one so we don't use the "code page escape" character In AddText(). But we are not using any part of the listtext section so we don't call AddText() on the "code page escape" character and therefore we don't clear FIgnoreChars until we move on to the next RTF section.
486-
487-For example KMemo with a Bullet Point writes an RTF file that looks like this -
462+KMemortf ignores "listtext" as an RTF tag (kmemortf#2381). It sets FActiveState.Group to RGUNKNOWN so other methods know not to do anything with it. However, it is still processed to some extent. In particular, if the listtext params include a unicode character, ReadSpecialCharacter() will set FIgnoreChars to one so we don't use the "code page escape" character In AddText(). But we are not using any part of the listtext section so we don't call AddText() on the "code page escape" character and therefore we don't clear FIgnoreChars until we move on to the next RTF section.
488463
489-```text
490- {\listtext{\f0\fs20\cf1 \u8226\'3F\tab }}
464+For example KMemo with a Bullet Point writes an RTF file that looks like this -
491465```
466+{\listtext{\f0\fs20\cf1 \u8226\'3F\tab }}
467+```
492468
493-The "u8226" is a bullet point character (it is ignored by KMemo). The "'3F' is the "?" character, there in case the app cannot handle Unicode escapes.
469+The "u8226" is a bullet point character (it is ignored by KMemo). The "'3F' is the "?" character, there in case the app cannot handle Unicode escapes.
494470
495-After that listtext block is processed FIgnore insists we still need to drop the first displayable character. So we loose first character of text following a bullet point.
471+After that listtext block is processed FIgnore insists we still need to drop the first displayable character. So we loose first character of text following a bullet point.
496472
497-The Solution - Line 2712 at the end of procedure TKMemoRTFReader.ReadSpecialCharacter(...) -
473+The Solution - Line 2712 at the end of procedure TKMemoRTFReader.ReadSpecialCharacter(...) -
474+```
475+if SetIgnoreChars then
476+ FIgnoreChars := FIgnoreCharsAfterUnicode;
477+```
498478
499-``` pascal
500- if SetIgnoreChars then
501- FIgnoreChars := FIgnoreCharsAfterUnicode;
479+becomes -
502480```
481+if SetIgnoreChars and (FActiveState.Group <> RGUNKNOWN) then // DRB - April 2018
482+ FIgnoreChars := FIgnoreCharsAfterUnicode;
483+```
503484
504-becomes -
485+# Executing commands
505486
506-``` pascal
507- if SetIgnoreChars and (FActiveState.Group <> RGUNKNOWN) then // DRB - April 2018
508- FIgnoreChars := FIgnoreCharsAfterUnicode;
487+Some commands can be executed to facilitate your work, including clipboard tasks. First _keditcommon_ shall be added in the _uses_ section.
509488```
489+...
490+ uses ...
491+ keditcommon...
492+```
510493
494+Then these commands are used the following way:
495+```
496+KMemo1.ExecuteCommand(ecSelectAll);
497+ KMemo1.ExecuteCommand(ecCopy);
498+ KMemo1.ExecuteCommand(ecCut);
499+ //etc
500+```
501+
502+
503+See the content of _TKEditCommand_ in _keditcommon_ for the list of the available commands.
504+
511505# Installation
512506
513-KMemo must be installed as part of the KControls Package. As is usual, its easy. You should be using a recent version of FPC and Lazarus. Download the Github vesion of KControls, unzip and put it somewhere suitable. Fire up Lazarus, click Packages, click "Open Pack File (.lpk)" and find the kcontrolslaz.lpk file in the kit you downloaded.
507+KMemo must be installed as part of the KControls Package. As is usual, its easy. You should be using a recent version of FPC and Lazarus. Download the Github vesion of KControls, unzip and put it somewhere suitable. Fire up Lazarus, click Packages, click "Open Pack File (.lpk)" and find the kcontrolslaz.lpk file in the kit you downloaded.
514508
515-Click Compile.
509+Click Compile.
516510
517-Lazarus will do its install thing, shortly will ask you about rebuilding, answer yes, it will then shutdown. On startup, you will find an extra row of components at the far right, KMemo amongst them.
511+Lazarus will do its install thing, shortly will ask you about rebuilding, answer yes, it will then shutdown. On startup, you will find an extra row of components at the far right, KMemo amongst them.
518512
519-If you find this as valuable a component as I did, I suggest you hit the link below and thank TK, it is one hell of an effort !
513+If you find this as valuable a component as I did, I suggest you hit the link below and thank TK, it is one hell of an effort !
520514
521515# See Also
522516
523-- [[KControls]] - the package KMemo is part of.
524-- <https://github.com/kryslt/KControls> - Github repository.
525-- [[RichMemo]] - an alternative package.
526-- [[UTF8 strings and characters]]
527-- How to hide / show the caret? - TBD
517+ * [[KControls]] \- the package KMemo is part of.
518+ * <https://github.com/kryslt/KControls> \- Github repository.
519+ * [[RichMemo]] \- an alternative package.
520+ * [[UTF8 strings and characters]]
521+ * How to hide / show the caret? - TBD
528522
529523---
530-*From the Free Pascal wiki, page "KControls/KMemo notes", as it stood on 2024-01-29 after a change by Dbannon, out of the wiki's dump of 2024-05-26. Copyright stays with the author who wrote it, according to the old wiki's [[Licensing]] page. If you have concerns about this material, [message the admin](/wiki-contact).*
531-Category: Components
524+*From the Free Pascal wiki, page "KControls/KMemo notes", as the Internet Archive kept it on 2025-06-08, last changed on 2024-12-17. Copyright stays with the author who wrote it, according to the old wiki's [[Licensing]] page. If you have concerns about this material, [message the admin](/wiki-contact).*

Back to history