Recent

Author Topic: TKMemo: How do I make it obey line feed, carriage returns?  (Read 2077 times)

RedOctober

  • Full Member
  • ***
  • Posts: 219
TKMemo: How do I make it obey line feed, carriage returns?
« on: March 10, 2018, 12:03:24 am »
When I do this:


Code: Pascal  [Select]
  1.   lyn := 'Hello' + #13#10 + 'World';
  2.   txt_blk := kemStat.Blocks.AddTextBlock(lyn);
  3.  

// The kemStat displays:   HelloWorld

If I do this:

Code: Pascal  [Select]
  1.   lyn := 'Hello' + #13#10 + 'World';
  2.   lyn := StringReplace(txt, #13#10, #$B6, [rfReplaceAll]); // Line 113 of kmemo.pas:  cNewLineChar = #$B6;
  3.   txt_blk := kemStat.Blocks.AddTextBlock(lyn);
  4.  

// The kemStat displays:   Hello?World

What I want is for the kemStat to display lines, broken at the #13#10 characters, just like a TMemo does.

How do I get KMemo to obey line breaks handed to it in a string?

Thanks in advance.

Platform:  Lazarus 1.8.1, TKMemo 1.7
« Last Edit: March 10, 2018, 12:06:57 am by RedOctober »

jamie

  • Hero Member
  • *****
  • Posts: 973
Re: TKMemo: How do I make it obey line feed, carriage returns?
« Reply #1 on: March 10, 2018, 12:37:11 am »
I don't know that component but if it will accept a TstringList then you can load a StringList which will
break at those points and then assign the list to the Kmemo etc..

RedOctober

  • Full Member
  • ***
  • Posts: 219
Re: TKMemo: How do I make it obey line feed, carriage returns?
« Reply #2 on: March 10, 2018, 02:07:20 am »
It will not accept a TStringList.

dbannon

  • Sr. Member
  • ****
  • Posts: 338
Re: TKMemo: How do I make it obey line feed, carriage returns?
« Reply #3 on: March 10, 2018, 02:10:38 am »
In TKMemo, all content is held in blocks.  You can have text blocks, paragraph blocks, link blocks etc.
If you want to force a newline, you add a TKParagraphBlock. The same thing happens when you press 'enter' while editing, the KMemo adds the TKPara for you.

From memory -
Code: Pascal  [Select]
  1. KMemo1.Blocks.AddParagraph();
Will add a paragraph marker at the end, you can give it a parameter, a block number, and that paragraph marker will be inserted at that block position.

If you are using KMemo, you must understand its block structure ! Blocks hold all the data about font characteristics, bullets etc.
Davo
Lazarus 1.8, Linux (and reluctantly Win10, OSX)

RedOctober

  • Full Member
  • ***
  • Posts: 219
Re: TKMemo: How do I make it obey line feed, carriage returns?
« Reply #4 on: March 10, 2018, 02:22:31 am »
Hi dbannon,

So, am I to understand that it is up to me to programmatically split my String, that contains #13#10 line breaks, into ... I guess a TStringsList, then use a for i := 0 loop to .AddTextBlock(); .AddParagraph; after each added text block?

Seems like a lot of extra overhead just to get KMemo to accept line feeds.

dbannon

  • Sr. Member
  • ****
  • Posts: 338
Re: TKMemo: How do I make it obey line feed, carriage returns?
« Reply #5 on: March 10, 2018, 02:39:34 am »
Yep, you need to replace newlines with para markers, easiest way is as you insert the text. Just a couple of lines of code.

And please don't forget that (again from memory) "LineEnding" is a better thing to search for than #10#13, thats a windows only way to think. Hmm, I think I seach for #10 and if I get one, check to see if next one is #13, if so, drop it on floor ! Silly windows ....

Davo

Lazarus 1.8, Linux (and reluctantly Win10, OSX)

RedOctober

  • Full Member
  • ***
  • Posts: 219
Re: TKMemo: How do I make it obey line feed, carriage returns?
« Reply #6 on: March 10, 2018, 02:46:56 am »
Ok, so what is the para marker character?

jamie

  • Hero Member
  • *****
  • Posts: 973
Re: TKMemo: How do I make it obey line feed, carriage returns?
« Reply #7 on: March 10, 2018, 03:13:15 am »
make a character parser to read the string, this parser should always remember the last character..

when you encounter a 13 for example, test the last character you processed, if its a 10 then you
know you have a line ending pair..
 
 if its not a line ending char, then read the next char, if that is a line ending and its not the same
as the first line ending then you have found a pair, if it is the same then you have found a blank line.
 
if the next char is not a line ending then you have found a line that uses only one character at the
end for a line ending..

 It gets complicated but I've dealt with this mess many times..

 normally its best to use a LineEnding function that can remember the last character it read and it will
return a code to indicate if the line is complete.

 if you want to see how such a function would look like just ask.


RedOctober

  • Full Member
  • ***
  • Posts: 219
Re: TKMemo: How do I make it obey line feed, carriage returns?
« Reply #8 on: March 10, 2018, 03:33:00 am »
I think we are getting way off on a tangent here.  According to all the respondents, my original code shd work, as long as I replace #13#10 with KMemo's "new line" character.  I tried that, it doesn't work.  I get Hello?World.

I understand I have to add text in a block.  As evidenced by my original post. 

My code from my original post:

Code: Pascal  [Select]
  1.   lyn := 'Hello' + #13#10 + 'World';
  2.   lyn := StringReplace(lyn, #13#10, #$B6, [rfReplaceAll]); // Line 113 of kmemo.pas:  cNewLineChar = #$B6;
  3.   txt_blk := kemStat.Blocks.AddTextBlock(lyn);
  4.  

What do I need to use instead of #$B6   ...?
« Last Edit: March 10, 2018, 03:48:02 am by RedOctober »

dbannon

  • Sr. Member
  • ****
  • Posts: 338
Re: TKMemo: How do I make it obey line feed, carriage returns?
« Reply #9 on: March 10, 2018, 11:26:36 am »
No, sorry, I was not clear enough.
KMemo content is a series of blocks. Some blocks are TKMemoTextBlock and they contain visible text. Some of them are TKParagraphBlock, they should not contain any text, they just sit there between the text and show where a new line needs to be.

Text blocks do not contain any newline characters.

Some untested stream of consciousness code -

Code: Pascal  [Select]
  1. procedure Form1.AddText(InSt : ANSIString; AddPara : Boolean = False);
  2. var
  3.     PB : TKMemoParagraph;    
  4. begin
  5.     TB := KM.Blocks.AddTextBlock(InStr);     // now set font etc characteristics
  6.     if AddPara then  
  7.         KM.Blocks.AddParagraph()
  8. end;
  9.  
  10. procedure TForm1.Import(St : ANSIString);
  11. var
  12.     Index : Integer = 0;
  13.     TempSt : ANSiString;
  14. begin
  15.     TempSt := '';
  16.     while Index < length(St) do begin
  17.          inc(Index);
  18.         if St[Index] = #10 then begin            
  19.              AddText(TempSt, True);
  20.              TempSt := '';
  21.         else begin
  22.               if St[Index] := #13 then continue;
  23.               TempSt := TempSt + St[Index];
  24.         end;
  25.         if length(TempSt) > 0 then
  26.              AddText(TempSt, False);
  27. end;

Now, I am not even sure that will compile, probably not. But it does show you what you need to know. Text Blocks do not contain any newlin characters. If you want a newline, insert a paragraph block.

Hmm, if you have a really simple case, its just possible you can assign to the Kmemo1.Blocks.Text property, it might do your newline conversions. I have not tried it, don't think so but possible ...

Code: Pascal  [Select]
  1. KM.Blocks.text := St

Let us know what you find .....

Davo
Lazarus 1.8, Linux (and reluctantly Win10, OSX)

RedOctober

  • Full Member
  • ***
  • Posts: 219
Re: TKMemo: How do I make it obey line feed, carriage returns?
« Reply #10 on: March 10, 2018, 10:43:29 pm »
I'm sure there is an easier way to do this, but I threw this together at high speed, tested it, and it works, so I'm going with it, until I can find out the correct way to get TKMemo to simply line break at line breaks.

Code: Pascal  [Select]
  1. procedure TfrmExecSQL.UpdateStatLog(const txt, txt_styl: String; add_blank_lines: Integer; proc_msgs: Boolean);
  2.  
  3.   procedure ColorTextBlock(const txt_blk: TKMemoTextBlock; const txt_styl: String);
  4.     begin
  5.       if SameText(txt_styl, 'Data') then
  6.         begin
  7.           txt_blk.TextStyle.Font.Color := clBlack;
  8.         end;
  9.  
  10.       if SameText(txt_styl, 'Attention') then
  11.         begin
  12.           txt_blk.TextStyle.Font.Color := clBlue;
  13.         end;
  14.  
  15.       if SameText(txt_styl, 'Error') then
  16.         begin
  17.           txt_blk.TextStyle.Font.Color := clRed;
  18.         end;
  19.     end;
  20.  
  21. var
  22.   i, j: Integer; lyns: TStringList;
  23.   txt_blk: TKMemoTextBlock; s: String;
  24. begin
  25.   if Pos(#13#10, txt) = 0 then
  26.     begin
  27.       txt_blk := kemStat.Blocks.AddTextBlock(txt);
  28.       ColorTextBlock(txt_blk, txt_styl);
  29.       kemStat.Blocks.AddParagraph;
  30.     end
  31.   else
  32.     begin
  33.       lyns := nil;
  34.       try
  35.         s := txt;
  36.         s := StringReplace(s, #13#10, '^', [rfReplaceAll]);
  37.  
  38.         lyns := TStringList.Create;
  39.         j := ExtractStrings(['^'], [' '], PChar(s), lyns, False);
  40.  
  41.         for i := 0 to Pred(j) do
  42.           begin
  43.             txt_blk := kemStat.Blocks.AddTextBlock(lyns[i]);
  44.             ColorTextBlock(txt_blk, txt_styl);
  45.             kemStat.Blocks.AddParagraph;
  46.           end;
  47.       finally
  48.         lyns.Free;
  49.       end;
  50.     end;
  51.  
  52.   for i := 0 to Pred(add_blank_lines) do
  53.     kemStat.Blocks.AddParagraph;
  54.  
  55.   kemStat.ExecuteCommand(ecEditorBottom);
  56.  
  57.   if proc_msgs then
  58.     Application.ProcessMessages;
  59.  
  60. end;
  61.  

dbannon

  • Sr. Member
  • ****
  • Posts: 338
Re: TKMemo: How do I make it obey line feed, carriage returns?
« Reply #11 on: March 11, 2018, 12:18:52 am »
OK, if it works for you, great. However, I think you might be over complicating it a little. And over complicated code is hard to debug and and even hard to understand what you were doing when you come back to it after some time.

So, firstly, you seem to be substituting a '^' for a LineEnding - I don't see any point in doing that. If your input string unexpectedly contains a '^' it will break your algorithm. My sample code treats a lineending as a lineending, thats what it is.

Just parse the whole string as it is. Search for a #10, thats a newline in any OS you are likely to come across. You don't need to do anything with #13, it really does not mean anything so I drop it on the floor.  You 'should' almost never come across a #13 by itself in a text file. So that would simplify your code somewhat.

Searching for the next #10 and using library functions to extract the text component is probably a little faster than my simpler way of iterating over exery char in the string but I don't think you will see a difference unless your input string is more than, say, 20K. But your way is pretty good anyway.

Next, I reckon a case statement might be a better way to handle ColourTextBlock(), it does make for code that is somewhat easier to write and a lot easier to read later on. But if you are happy with it as it is, stick with it !

And, this is purely a personal thing, I don't like single letter variable names. Var names should remind you what they are going to be used for. Generally .....

Davo
Lazarus 1.8, Linux (and reluctantly Win10, OSX)