Hello!
I'm a new user, investigating transitioning from Livecode. It seems that there isn't an easy cross platform way to use rich text / styled text in the EditorGadget? Is this true? If so, what's the best way to use rich / styled text in PB?
Note: I mainly want to be able to use strikethrough styling on individual lines of text.
Thanks in advance.
Cross Platform Rich Text Editor
Re: Cross Platform Rich Text Editor
I can think of three extension solutions from memory.
1. idle's lightweight editorgadgetEx
2. PBEdit - a Canvas-based Texteditor by Mr.L
3. EditorEx by Thorsten1867
In addition there are also markdown editor implementations.
Not to forget the scintilla dll used in Pb
Maybe there is more.
1. idle's lightweight editorgadgetEx
2. PBEdit - a Canvas-based Texteditor by Mr.L
3. EditorEx by Thorsten1867
In addition there are also markdown editor implementations.
Not to forget the scintilla dll used in Pb
Maybe there is more.
Just because it worked doesn't mean it works.
PureBasic 6.04 (x86) and <latest stable version and current alpha/beta> (x64) on Windows 11 Home. Now started with Linux (VM: Ubuntu 22.04).
PureBasic 6.04 (x86) and <latest stable version and current alpha/beta> (x64) on Windows 11 Home. Now started with Linux (VM: Ubuntu 22.04).
Re: Cross Platform Rich Text Editor
Indeed there is no easy cross-platform way. But already 5 years ago I have posted this cross-platform code example in the German forum which allows to use text tags to convert marked text into bold, italic and underlined text. In MacOS and Windows the text is transformed into the RTF text format. Since Linux doesn't support the RTF format, the marked text is converted using GTK tags. Therefore you currently have to exchange marked text between the different operating systems before displaying it in the EditorGadget because the text in Linux - as mentioned - doesn't use the RTF format. But my special format before displaying it in the EditorGadget is cross-platform.russellm72 wrote: Tue Jul 19, 2022 3:14 pm It seems that there isn't an easy cross platform way to use rich text / styled text in the EditorGadget? Is this true? If so, what's the best way to use rich / styled text in PB?
I have modified the source code to also support strikethrough text by extending the tag definitions in the data section. I have successfully tested the modified source code on these operating systems:
- Linux Mint 19.3 'Tricia' x64 with Cinnamon and both GTK2 and GTK3 using PB 6.00 x64
- MacOS 10.13.6 'High Sierra' with PB 6.00 x64
- MacOS 11.6.7 'Big Sur' with PB 6.00 x64 in Light and Dark Mode
- Raspbian GNU/Linux 11 (Bullseye) ARM32 with PB 6.00 ARM32 (C backend)
- Windows 10 x64 21H2 with PB 6.00 x86 and x64
Linux Mint 19.3 'Tricia' with Cinnamon and GTK3

MacOS 10.13.6 'High Sierra'

Raspbian GNU/Linux 11 (Bullseye)

Windows 10 21H2

Code: Select all
EnableExplicit
CompilerIf #PB_Compiler_OS = #PB_OS_Linux
ImportC ""
gtk_text_buffer_create_tag(*Buffer.GtkTextBuffer, TagCharacter.P-UTF8,
PropertyName.P-UTF8, PropertyValue.I, Terminator.I = 0)
EndImport
CompilerEndIf
Structure TagEntry
Character.S
AttributeName.S
StartOffset.I
EndOffset.I
EndStructure
Procedure AddTextWithAttributes(EditorGadgetID.I, AttributeText.S)
Static NewList Tag.TagEntry()
Protected Offset.I
Protected RTFTag.S
Protected TagCharacter.S
Protected TagList.S
Protected TagOffset.I
CompilerSelect #PB_Compiler_OS
CompilerCase #PB_OS_Linux
Static TagTable.I
Protected AttributeValue.S
Protected EndIter.GtkTextIter
Protected NewTextOffset.I
Protected StartIter.GtkTextIter
Protected *TextBuffer = gtk_text_view_get_buffer_(0 +
GadgetID(EditorGadgetID))
CompilerCase #PB_OS_MacOS
Protected AttributeString.I
Protected AttributeTextASCII.S
Protected DataObject.I
Protected TextStorage.I
CompilerCase #PB_OS_Windows
Protected AttributeTextASCII.S
CompilerEndSelect
; ----- On first call create new tag table and define RTF tags (for MacOS and
; and Windows) or the Linux-specific tags
If ListSize(Tag()) = 0
Repeat
Read.S TagCharacter
If TagCharacter = ""
Break
Else
AddElement(Tag())
Tag()\Character = TagCharacter
Read.S Tag()\AttributeName
CompilerIf #PB_Compiler_OS = #PB_OS_Linux
Read.S AttributeValue
gtk_text_buffer_create_tag(*TextBuffer, Tag()\Character,
Tag()\AttributeName, Val(AttributeValue))
CompilerEndIf
EndIf
ForEver
Else
ForEach Tag()
Tag()\StartOffset = 0
Tag()\EndOffset = 0
Next
EndIf
; ----- Find all tags and fill in tag character, attribute name, start-
; and stop offset in LinkedList Tag()
Repeat
Offset = FindString(AttributeText, "[")
If Offset = 0
Break
Else
TagList = ""
TagOffset = Offset + 1
RTFTag = "{"
Repeat
TagCharacter = Mid(AttributeText, TagOffset, 1)
If TagCharacter = "]"
Break
Else
If FindString(TagList, TagCharacter) = 0
TagList + TagCharacter
EndIf
ForEach Tag()
If Tag()\Character = TagCharacter
If Tag()\StartOffset = 0
Tag()\StartOffset = Offset
RTFTag + "\" + Tag()\AttributeName + " "
Else
Tag()\EndOffset = Offset
RTFTag = "}"
EndIf
Break
EndIf
Next
EndIf
TagOffset + 1
ForEver
CompilerIf #PB_Compiler_OS = #PB_OS_Linux
AttributeText = ReplaceString(AttributeText, "[" + TagList + "]", "",
#PB_String_NoCase, Offset, 1)
CompilerElse
AttributeText = ReplaceString(AttributeText, "[" + TagList + "]",
RTFTag, #PB_String_NoCase, Offset, 1)
AttributeText = ReplaceString(AttributeText, #CR$, "\line")
CompilerEndIf
TagList = ""
EndIf
ForEver
; ----- Append new text with attributes at already existing text
CompilerSelect #PB_Compiler_OS
CompilerCase #PB_OS_Linux
NewTextOffset = gtk_text_buffer_get_char_count_(*TextBuffer)
gtk_text_buffer_get_end_iter_(*TextBuffer, @EndIter)
gtk_text_buffer_insert_(*TextBuffer, @EndIter, AttributeText, -1)
If ListSize(Tag()) > 0
; ----- Fill in all tags from list in *TextBuffer
ForEach Tag()
gtk_text_buffer_get_iter_at_offset_(*TextBuffer, @StartIter,
NewTextOffset + Tag()\StartOffset - 1)
gtk_text_buffer_get_iter_at_offset_(*TextBuffer, @EndIter,
NewTextOffset + Tag()\EndOffset - 1)
gtk_text_buffer_apply_tag_by_name_(*TextBuffer, Tag()\Character,
@StartIter, @EndIter)
Next
EndIf
CompilerCase #PB_OS_MacOS
AttributeText = "{\rtf1" + AttributeText + "}"
AttributeTextASCII = Space(StringByteLength(AttributeText, #PB_Ascii))
PokeS(@AttributeTextASCII, AttributeText, -1, #PB_Ascii)
DataObject = CocoaMessage(0, 0,
"NSData dataWithBytes:", @AttributeTextASCII,
"length:", Len(AttributeText))
If DataObject
AttributeString = CocoaMessage(0, 0, "NSAttributedString alloc")
CocoaMessage(@AttributeString, AttributeString,
"initWithRTF:@", @DataObject,
"documentAttributes:", 0)
If AttributeString
TextStorage = CocoaMessage(0, GadgetID(0), "textStorage")
CocoaMessage(0, TextStorage, "appendAttributedString:",
AttributeString)
CocoaMessage(0, AttributeString, "release")
EndIf
; ----- Automatically set correct text color in light and dark mode
CocoaMessage(0, GadgetID(EditorGadgetID),
"setTextColor:", CocoaMessage(0, 0, "NSColor textColor"))
EndIf
CompilerCase #PB_OS_Windows
AttributeText = "{\rtf1" + AttributeText + "}"
AttributeTextASCII = Space(StringByteLength(AttributeText, #PB_Ascii))
PokeS(@AttributeTextASCII, AttributeText, -1, #PB_Ascii)
SendMessage_(GadgetID(EditorGadgetID), #EM_SETSEL, -1, -1)
SendMessage_(GadgetID(EditorGadgetID), #EM_REPLACESEL, 0,
AttributeTextASCII)
CompilerEndSelect
EndProcedure
OpenWindow(0, 100, 100, 400, 100, "EditorGadget with text attributes")
EditorGadget(0, 10, 10, WindowWidth(0) - 20, WindowHeight(0) - 20)
AddTextWithAttributes(0,
"This is [b]bold text[b] and the following is [u]underlined[u]." + #CR$)
AddTextWithAttributes(0, "Now follows [i]italic[i] and [s]strikethrough[s]." +
#CR$)
AddTextWithAttributes(0,
"And combined [biu]bold, italic and underlined[biu].")
Repeat
Until WaitWindowEvent() = #PB_Event_CloseWindow
End
; ----- Tag definitions
DataSection
CompilerIf #PB_Compiler_OS = #PB_OS_Linux
Data.S "b", "weight", "700"
Data.S "i", "style", "2"
Data.S "s", "strikethrough", "1"
Data.S "u", "underline", "1"
CompilerElse
Data.S "b", "b"
Data.S "i", "i"
Data.S "s", "strike"
Data.S "u", "ul"
CompilerEndIf
Data.S ""
EndDataSection
Last edited by Shardik on Wed Jul 20, 2022 9:33 pm, edited 2 times in total.
-
- User
- Posts: 17
- Joined: Wed Jul 13, 2022 2:04 pm
Re: Cross Platform Rich Text Editor
Wow! Thank you! This looks really cool. I've been reading through your code a bit trying to get familiar with it and to understand it.But already 5 years ago I have posted this cross-platform code example in the German forum which allows to use text tags to convert marked text into bold, italic and underlined text.
One thing I'm trying to wrap my head around is how I would change one line of text within an EditGadget.
I'm used to being able to do something like this (in Livecode):
Code: Select all
set the textStyle of line tCurrLineNum of field "FieldCommandsList" to "strikeout"
Code: Select all
set textStyle of line tIndex of field "FieldCommandsList" to "plain"
Thanks in advance for any guidance or advice. And, seriously, thanks again for sharing this, it looks really promising and very impressive.
Re: Cross Platform Rich Text Editor
Thank you for your praise and interest. Until now you have been the first one. Because of the missing general interest I didn't develop the code any further.russellm72 wrote: Wed Jul 20, 2022 4:12 pm Wow! Thank you! This looks really cool. I've been reading through your code a bit trying to get familiar with it and to understand it.
russellm72 wrote: Wed Jul 20, 2022 4:12 pm One thing I'm trying to wrap my head around is how I would change one line of text within an EditGadget
Until now you are only able to display preformatted text cross-platform with these predefined tags as defined in the DataSection:
Code: Select all
[b]Text[b] = display "Text" in bold
[i]Text[i] = display "Text" in italics
[s]Text[s] = display "Text" as strikethrough
[u]Text[u] = display "Text" as underlined
If you have the need to change the format of a text selected with the cursor dynamically, the example code has to be expanded! Please tell me whether you need this additional feature and I might try to implement it.
If you want to change the text format of an existing text in the EditorGadget by position index and string length or by searching for a text string and changing its format, this would be much easier to implement.
Re: Cross Platform Rich Text Editor
We really need to work more at saying thankyou and showing some appreciation. Looks good Shardik I might use it.
-
- User
- Posts: 17
- Joined: Wed Jul 13, 2022 2:04 pm
Re: Cross Platform Rich Text Editor
So, for my immediate needs, I would just need the ability to add or remove the strikethrough style from one or more lines of text. I'm guess from what you're saying that that would be by position index and string length? I haven't even investigated enough to know how to identify a line of text using PB because I've been very spoiled in Livecode with its ability to just use line numbers within any given chunk of text. I don't know, but I'm guessing that in PB you have to iterate character by character looking for line endings and keep track of their offsets in order to identify lines of text?Shardik wrote: Wed Jul 20, 2022 8:07 pm If you have the need to change the format of a text selected with the cursor dynamically, the example code has to be expanded! Please tell me whether you need this additional feature and I might try to implement it.
If you want to change the text format of an existing text in the EditorGadget by position index and string length or by searching for a text string and changing its format, this would be much easier to implement.
However, in thinking about things, I do think your code would be much more useful if it had the ability to reverse the process and take RTF/styled text from the EditGadget and convert it back to text with simple tags. Saying that, I have no idea how difficult a task that would be but it definitely would be extremely useful.
Anyway, thanks again for providing your code. It does look very promising.
Re: Cross Platform Rich Text Editor
Linux (tested successfully on Linux Mint 19.3 'Tricia' x64 Cinnamon and PB 6.00 x64):russellm72 wrote: Thu Jul 21, 2022 10:15 am So, for my immediate needs, I would just need the ability to add or remove the strikethrough style from one or more lines of text.

MacOS (tested successfully on MacOS Catalina and Big Sur with PB 6.00):

Windows (tested successfully on Windows 10 x64 21H2 with PB 6.00 x86):

Code: Select all
EnableExplicit
Define FontSize.I
Define i.I
Define LineCount.I
Define LineNumber.I
Define TextLine.S
Define WindowHeight.I
#Window = 0
Enumeration Gadgets
#Editor
#Text
#ComboBox
EndEnumeration
Declare ToggleStrikethroughInLine(EditorID.I, LineNumber.I)
CompilerSelect #PB_Compiler_OS
CompilerCase #PB_OS_Linux
FontSize = 21
CompilerCase #PB_OS_MacOS
#NSUnderlineStyleThick = 2
FontSize = 27
CompilerDefault
FontSize = 21
CompilerEndSelect
OpenWindow(#Window, 200, 100, 270, 194, "EditorGadget")
EditorGadget(#Editor, 23, 20, WindowWidth(#Window) - 45,
WindowHeight(#Window) - 90)
SetGadgetFont(#Editor, LoadFont(0, "Arial", FontSize))
TextGadget(#Text, 10, GadgetHeight(#Editor) + 40, 180, 25,
"Toggle strikethrough in line", #PB_Text_Right)
ComboBoxGadget(#ComboBox, GadgetWidth(#Text) + 18, GadgetHeight(#Editor) + 37,
50, 25)
CompilerIf #PB_Compiler_OS = #PB_OS_Linux
; ----- Define Callback when popup list of ComboBox is closed because on
; Linux the non-editable ComboBox doesn't produce a change event, if
; the currently selected item is chosen again
ProcedureC ComboBoxPopupCallback(*ComboBox.GtkComboBox,
*ParamSpec.GParamSpec, *UserData)
Protected IsComboBoxPopupOpen.I
g_object_get_(GadgetID(#ComboBox), "popup-shown", @IsComboBoxPopupOpen)
If IsComboBoxPopupOpen = #False
PostEvent(#PB_Event_Gadget, #Window, #ComboBox, #PB_EventType_Change)
EndIf
EndProcedure
g_signal_connect_(GadgetID(#ComboBox), "notify::popup-shown",
@ComboBoxPopupCallback(), 0)
CompilerEndIf
Repeat
Read.S TextLine
If TextLine <> #EOT$
AddGadgetItem(#Editor, -1, TextLine)
LineCount + 1
EndIf
Until TextLine = #EOT$
For i = 0 To LineCount - 1
AddGadgetItem(#ComboBox, -1, Str(i + 1))
Next i
Dim IsStrikethrough.I(LineCount - 1)
SetGadgetState(#ComboBox, 0)
Repeat
Select WaitWindowEvent()
Case #PB_Event_CloseWindow
Break
Case #PB_Event_Gadget
If EventGadget() = #ComboBox
If EventType() = #PB_EventType_Change
LineNumber = GetGadgetState(#ComboBox)
ToggleStrikethroughInLine(#Editor, LineNumber)
EndIf
EndIf
EndSelect
ForEver
End
Procedure ToggleStrikethroughInLine(EditorID.I, LineNumber.I)
Shared IsStrikethrough.I()
Protected TextLine.S
TextLine = GetGadgetItemText(EditorID, LineNumber)
IsStrikethrough(LineNumber) ! 1
CompilerSelect #PB_Compiler_OS
CompilerCase #PB_OS_Linux ; -----------------------------------------------
Static *StrikethroughTag.GtkTextTag
Protected EndIter.GtkTextIter
Protected StartIter.GtkTextIter
Protected *TextBuffer
*TextBuffer = gtk_text_view_get_buffer_(GadgetID(EditorID))
gtk_text_buffer_get_iter_at_line_(*TextBuffer,
@StartIter, LineNumber)
gtk_text_buffer_get_iter_at_offset_(*TextBuffer,
@EndIter, gtk_text_iter_get_offset_(@StartIter) + Len(TextLine))
If *StrikethroughTag = 0
*StrikethroughTag = gtk_text_buffer_create_tag_(*TextBuffer,
"s", "strikethrough", #True)
EndIf
If IsStrikethrough(LineNumber)
gtk_text_buffer_apply_tag_(*TextBuffer, *StrikethroughTag,
@StartIter, @EndIter)
Else
gtk_text_buffer_remove_tag_(*TextBuffer, *StrikethroughTag,
@StartIter, @EndIter)
EndIf
CompilerCase #PB_OS_MacOS ; -----------------------------------------------
Protected AttributedString.I
Protected i.I
Protected Location.I
Protected Range.NSRange
Protected TextStorage.I
TextStorage = CocoaMessage(0, GadgetID(EditorID), "textStorage")
AttributedString = CocoaMessage(0, CocoaMessage(0, 0,
"NSMutableAttributedString alloc"), "initWithString:$", @TextLine)
For i = 0 To LineNumber - 1
Location + Len(GetGadgetItemText(EditorID, LineNumber)) + 2
Next i
Range\location = Location
Range\length = Len(TextLine) + 1
If IsStrikethrough(LineNumber)
CocoaMessage(0, TextStorage,
"addAttribute:$", @"NSStrikethrough",
"value:", CocoaMessage(0, 0,
"NSNumber numberWithInteger:", #NSUnderlineStyleThick),
"range:@", @Range)
CocoaMessage(0, AttributedString, "release")
Else
CocoaMessage(0, TextStorage,
"removeAttribute:$", @"NSStrikethrough",
"range:@", @Range)
EndIf
CompilerCase #PB_OS_Windows ; ---------------------------------------------
Protected Format.CHARFORMAT2
Protected Range.CHARRANGE
; ----- Select text
Range\cpMin = SendMessage_(GadgetID(0), #EM_LINEINDEX,
LineNumber, 0)
Range\cpMax = Range\cpMin + Len(TextLine)
SendMessage_(GadgetID(EditorID), #EM_EXSETSEL, 0, @Range)
; ----- Set/reset strikethrough
Format\cbSize = SizeOf(CHARFORMAT2)
Format\dwMask = #CFM_STRIKEOUT
If IsStrikethrough(LineNumber)
Format\dwEffects = #CFM_STRIKEOUT
Else
Format\dwEffects = 0
EndIf
SendMessage_(GadgetID(EditorID), #EM_SETCHARFORMAT, #SCF_SELECTION,
@Format)
; ----- Unselect text
SendMessage_(GadgetID(EditorID), #EM_HIDESELECTION, 1, 0)
CompilerEndSelect
EndProcedure
DataSection
Data.S "The quick brown"
Data.S "fox jumps over"
Data.S "the lazy dog."
Data.S #EOT$
EndDataSection