Paragraph formatting—Bullet pointsRich text editors provide several styles for numbering; accessing TOM provides additional styles eg numbers in circles.
The following constants set the format of the paragraph:
tomListNone - Removes paragraph bullets
tomListBullet - Inserts a circular black bullet
tomListNumberAsArabic - Normal numbering (0, 1, 2,…) followed by a round bracket
tomListNumberAsLCLetter - Lower case lettering followed by a round bracket
tomListNumberAsUCLetter - Upper case lettering followed by a round bracket
tomListNumberAsLCRoman - Lower case Roman numbering followed by a round bracket
tomListNumberAsUCRoman - Upper case Roman numbering followed by a round bracket
tomListNumberAsSequence - Seems to be the same as tomListNumberAsArabic
tomListNumberedCircle - Number placed in plain circle followed by a round bracket
tomListNumberedBlackCircleWingding - Number placed in black circle followed by a round bracket
tomListNumberedWhiteCircleWingding - Seems to be the same as tomListNumberedCircle
tomListNumberedArabicWide - Normal numbering with larger line spacing
The following constants set the decoration around the number:
tomListParentheses - Puts round brackets around the number
tomListPeriod - Puts a full stop after the number
tomListPlain - Uses plain numbering without brackets of full stops
tomListMinus - Puts a hyphen after the number
The constants for formatting the paragraph can be OR'd with the constants for the decoration around the number eg
tomListNumberedCircle | tomListPlain
Some of these constants were missing from the original Tom.h file so I have inserted them.
The following function shows how to format paragraph bullets using TOM. It will number the selected paragraph(s) or just the paragraph containing the caret, placing the number in a circle:
void TOM_SetParaBullet(HWND hEdit){
//Check that the TOM interface has been initialised
IS_TOM_INIT
long int startPos=0;
long int endPos = 0;
HRESULT hr;
ITextRange *textRange = NULL;
ITextPara *textPara = NULL;
//Get the start and end position of selected paragraph(s)
SendMessage(hEdit, EM_GETSEL, (WPARAM)&startPos, (LPARAM)&endPos);
//Initialise the ITextRange object with the start and end positions
hr = TextDocument2->lpVtbl->Range(TextDocument2, startPos, endPos, &textRange);
TOM_ShowErrorMessage(hr);
//Initialise the ITextPara object
hr = textRange->lpVtbl->GetPara(textRange, &textPara);
TOM_ShowErrorMessage(hr);
//Format the selected paragraph(s) with a number in a circle and no bracket decorations
hr = textPara->lpVtbl->SetListType(textPara, tomListNumberedCircle | tomListPlain);
TOM_ShowErrorMessage(hr);
//Free the ITextPara object
textPara->lpVtbl->Release(textPara);
//TOM_ShowErrorMessage(hr);
//Free the ITextRange object
textRange->lpVtbl->Release(textRange); //This does not return a HRESULT type
return;
}
The normal Windows API call using EM_GETSEL is used to get the selected paragraph(s). This data is used to initialise the ITextRange object and then the ITextPara object is initialised using the selected range. Next, the selected paragraphs are formatted using the SetListType method of the ITextPara object. Finally, the pointers for textPara and textRange are freed.
By default, the numbering style set by SetListType will include a round bracket after the number. This can be removed by ORing the TOM constant that sets the style with tomListPlain. In this function, the TOM constant tomListNumberedCircle is OR'd with tomListPlain to prevent a round bracket appearing after the circled number.
Experiment showed that ORing the ASCII code for '*' with tomListNumberAsArabic will provide asterisked bullet points:
textPara->lpVtbl->SetListType(textPara, tomListNumberAsArabic | 0x2A);
Possibly other symbols can be used as bullet points....
Paragraph formatting—otherThe previous function can be extended to provide additional paragraph formatting eg paragraph spacing. Although this can be achieved by sending normal messages to the Editor Gadget, I am including this here as a demonstration of what can be achieved directly with TOM. Use the following TOM constants to set paragraph spacing:
tomLineSpaceSingle - Normal line spacing. The floating point argument is ignored
tomLineSpace1pt5 - Line spacing of 1.5 The floating point argument is ignored
tomLineSpaceDouble - Double line spacing. The floating point argument is ignored
tomLineSpaceAtLeast - Spacing is set by the floating point argument. If this is less than that for single spacing then the paragraph is single spaced
tomLineSpaceExactly - Spacing is set by the floating point argument and can be less than that of single spacing.
tomLineSpaceMultiple - Line spacing is set in terms of lines.
tomLineSpacePercent - Set line spacing by percent of line height.
These constants are used with the SetLineSpacing method of the ITextPara object. This method requires three arguments: the first argument is the pointer for the ITextPara object; the second argument is one of the above constants to indicate the type of line spacing to use; the third argument is a float value that sets the value of the line spacing (this does not always seems to be used but must be included when calling this method).
The following function will put a bullet point at the start of the selected paragraph(s) and sets the paragraphs to use double line spacing:
void DoubleSpacedParagraph(HWND hEdit){
//Check that the TOM interface has been initialised
IS_TOM_INIT
long int startPos=0;
long int endPos = 0;
HRESULT hr;
ITextRange *textRange = NULL;
ITextPara *textPara = NULL;
//Get the start and end positions of the selected paragraph(s)
SendMessage(hEdit, EM_GETSEL, (WPARAM)&startPos, (LPARAM)&endPos);
//Initialise the ITextRange object with the start and end positions
hr = TextDocument2->lpVtbl->Range(TextDocument2, startPos, endPos, &textRange);
TOM_ShowErrorMessage(hr);
//Initialise the ITextPara object
hr = textRange->lpVtbl->GetPara(textRange, &textPara);
TOM_ShowErrorMessage(hr);
//Set the line spacing
hr = textPara->lpVtbl->SetLineSpacing(textPara, tomLineSpaceDouble, 0);
TOM_ShowErrorMessage(hr);
//Format the selected paragraph(s) with a bullet
hr = textPara->lpVtbl->SetListType(textPara, tomListBullet);
TOM_ShowErrorMessage(hr);
//Free the ITextPara object
textPara->lpVtbl->Release(textPara);
//TOM_ShowErrorMessage(hr);
//Free the ITextRange object
textRange->lpVtbl->Release(textRange);
return;
}
Get the character, word, line, sentence or paragraph indexThis may be useful, for example, if you want to display line numbers in a rich text editor where the height of each line may be different when the height of fonts change on different lines. The line numbers could be displayed in a separate rich text editor next to the one that contains the text. You could get the paragraph index and display this in a status bar as paragraph number.
To get the index for one of these items, the GetIndex() method of the ITextSelection is required. This has two parameters. The first is one of the above constants to tell the object which index is required. The second is the address of a variable that will store the index.
The following function will obtain the index of the line containing the cursor.
long int GetLineIndex(void){
//Check that the TOM interface has been initialised
IS_TOM_INIT_RETURN
long int LineIndex =0;
HRESULT hr;
ITextSelection *textSelection = NULL;
//Initialise the ITextSelection object
TextDocument2->lpVtbl->GetSelection(TextDocument2, &textSelection);
//Get the index of the line containing the caret
hr = textSelection->lpVtbl->GetIndex(textSelection, tomLine, &LineIndex);
TOM_ShowErrorMessage(hr);
//Free the ITextSelection object
textSelection->lpVtbl->Release(textSelection);
return LineIndex;
}
The index is obtained using the GetIndex()method of the ITextSelection object. This has three arguments. The first is a pointer to the ITextSelection object; the second argument is one of the TOM constants to tell the ITextrange object which index is required (character, word, sentence, etc); the third is the address of a variable that will store the index.
When indexing words, end of line markers and full stops is included in the result.
Underlining textTOM may be used to underline words using a wide variety of different underlining styles. For example, it is possible to underline text with dotted lines, dashed lines, and wavy lines. Wavy lines are used in word processors to indicate spelling mistakes. Use the following TOM constants (listed on the Microsoft website at
https://learn.microsoft.com/en-us/windows/win32/api/tom/ne-tom-tomconstants) to set the underlining style:
tomSingle - Single underline
tomWords - Underline words only
tomDouble - Double underline
tomDotted - Dotted underline
tomDash - Dash underline
tomDashDot - Dash dot underline
tomDashDotDot - Dash dot dot underline
tomWave - Wave underline
tomThick - Thick line
tomHair - Hair line
tomDoubleWave - Double wave
tomHeavyWave - Heavy wave underline
tomLongDash - Long dash underline
tomThickDash - Thick dash underline
tomThickDashDot - Thick dash dot underline
tomThickDashDotDot - Thick dash dot dot underline
tomThickDotted - Thick dotted underline
tomThickLongDash - Thick long dash underline
The following function demonstrates how to underline selected text with a dotted line.
void TOM_UnderlineText(HWND hRichEd){
//Check TOM has been initialised
IS_TOM_INIT
long int startPos = 0;
long int endPos = 0;
HRESULT hr;
ITextRange *textRange = NULL;
ITextFont *textFont = NULL;
//Get the start and end position of the selected text
SendMessage(hRichEd, EM_GETSEL, (WPARAM)&startPos, (LPARAM)&endPos);
//Initialise the ITextRange with start and end positions of the selected text
hr = TextDocument2->lpVtbl->Range(TextDocument2, startPos, endPos, &textRange);
TOM_ShowErrorMessage(hr);
/*
//Can use this to select whole words and (unfortunately) the following space
long int CharsSelected = 0;
hr = textRange->lpVtbl->Expand(textRange, tomWord, &CharsSelected);
TOM_ShowErrorMessage(hr);
*/
//Initialise the ITextFont object
hr = textRange->lpVtbl->GetFont(textRange, &textFont);
TOM_ShowErrorMessage(hr);
//Draw a dotted line under the selected text
hr = textFont->lpVtbl->SetUnderline(textFont, tomDotted);
TOM_ShowErrorMessage(hr);
//Free the ITextFont object
hr = textFont->lpVtbl->Release(textFont);
TOM_ShowErrorMessage(hr);
//Free the ITextRange object
hr = textRange->lpVtbl->Release(textRange);
TOM_ShowErrorMessage(hr);
}
The function gets the selected text and then the method GetFont() initialises an ITextFont object.
Calling its method, SetUnderline(), draws the line in the style set by the parameter passed to this method.
The last two lines frees the ITextFont object and the ITextRange object.
This next function demonstrates how to remove underlining from selected text.
void TOM_RemoveUnderlineText(HWND hRichEd){
//Check TOM has been initialised
IS_TOM_INIT
long int startPos = 0;
long int endPos = 0;
HRESULT hr;
ITextRange *textRange = NULL;
ITextFont *textFont = NULL;
//Get the start and end positions of selected text
SendMessage(hRichEd, EM_GETSEL, (WPARAM)&startPos, (LPARAM)&endPos);
//Initialise the ITextRange object with the start and end positions of selected text
hr = TextDocument2->lpVtbl->Range(TextDocument2, startPos, endPos, &textRange);
TOM_ShowErrorMessage(hr);
/*
//Can use this to select whole words and (unfortunately) the following space
long int CharsSelected = 0;
hr = textRange->lpVtbl->Expand(textRange, tomWord, &CharsSelected);
TOM_ShowErrorMessage(hr);
*/
//Initialise the ITextFont object
hr = textRange->lpVtbl->GetFont(textRange, &textFont);
TOM_ShowErrorMessage(hr);
//Remove underlining from the selected text
hr = textFont->lpVtbl->SetUnderline(textFont, tomNone);
TOM_ShowErrorMessage(hr);
//Free the ITextFont object
hr = textFont->lpVtbl->Release(textFont);
TOM_ShowErrorMessage(hr);
//Free the ITextRange object
hr = textRange->lpVtbl->Release(textRange);
TOM_ShowErrorMessage(hr);
}
The function is similar to that for underlining a word except that a parameter for SetUnderline() is changed to tomNone.
Issue with words that are already underlinedDocuments may contain words that are underlined using black straight lines—the normal underlining style. If the first function is used to underline a word that already has the normal underlining style then when the second function runs it will not restore the original underlining style—it removes all underlining from the word. This could cause a problem in a word-processing program that underlines misspelt words with a red wavy line. When the word is corrected then the original underlining will disappear—it would be necessary to keep track of every underlined word so that the original underlining can be restored to these words. Even worse, if the document contains valid words or expressions that are not in the dictionary (eg program code or mathematical terms) then these will be identified as being misspelt and when the document is saved the red wavy lines will be saved with the document!
Fortunately TOM provides an alternative way of underlining words that resolves this problem; with TOM it is possible to use temporary underlining (other temporary styles can be applied to characters too, if required). When temporary underlining is removed the original underlining style (and character style) is restored; moreover, temporary underlining is not stored in the document file when the document is saved.
Implementing temporary underliningThis function shows how to implement temporary underlining. It will underline the word above the cursor with a temporary red wavy line. It takes one argument, hRichEd, the handle of the rich text control that contains the text to be underlined.
void TOM_DrawTempWavyLine(HWND hRichEd){
//Check TOM has been initialised
IS_TOM_INIT
long int startPos=0;
long int endPos = 0;
HRESULT hr;
ITextRange *textRange = NULL;
ITextFont *textFont = NULL;
//Get start and end position of selected text
SendMessage(hRichEd, EM_GETSEL, (WPARAM)&startPos, (LPARAM)&endPos);
//Initialise the ITextRange object with the start and end positions of the selected characters
hr = TextDocument2->lpVtbl->Range(TextDocument2, startPos, endPos, &textRange);
TOM_ShowErrorMessage(hr);
//Initialise the ITextFont object
hr = textRange->lpVtbl->GetFont(textRange, &textFont);
TOM_ShowErrorMessage(hr);
//Make the line temporary
hr = textFont->lpVtbl->Reset(textFont, tomApplyTmp);
TOM_ShowErrorMessage(hr);
//Prepare to draw a temporary wavy line under the selected text
hr = textFont->lpVtbl->SetUnderline(textFont, tomWave);
TOM_ShowErrorMessage(hr);
//Set colour of the wavy line
// textFont->lpVtbl->SetUnderline(textFont, 0x0600+tomWave); //Draws temporary wavy line in black
// textFont->lpVtbl->SetUnderline(textFont, 0xFFFF0000); //Draws temporary wavy line in blue
hr = textFont->lpVtbl->SetUnderline(textFont, 0xFF0000FF); //Draws temporary wavy line in red
TOM_ShowErrorMessage(hr);
//Apply a wavy line to selected text
hr = textFont->lpVtbl->Reset(textFont, tomApplyNow);
TOM_ShowErrorMessage(hr);
//Free the ITextFont object
hr = textFont->lpVtbl->Release(textFont);
TOM_ShowErrorMessage(hr);
//Free the ITextRange object
hr = textRange->lpVtbl->Release(textRange);
TOM_ShowErrorMessage(hr);
}
After setting up the ITextFont object, its method Reset() is called with the argument tomApplyTmp to use temporary formatting. This method is called again to set the colour for underlining words. In this case the argument 0xFF0000FF is used. The last six digits control the colour of the line (These seem to be the colour values for RGB in reverse, so for a red line use 0xFF0000FF).
Then the method Reset() is called with the argument tomApplyNow to apply the underlining.
Finally, the ITextFont object is freed by calling its method Release()
The next function shows how to remove temporary underlining from selected text.
void TOM_RemoveTempUnderline(HWND hRichEd){
//Check TOM has been initialised
IS_TOM_INIT
long int startPos=0;
long int endPos = 0;
HRESULT hr;
ITextRange *textRange = NULL;
ITextFont *textFont = NULL;
//Get the start and end position of selected text
SendMessage(hRichEd, EM_GETSEL, (WPARAM)&startPos, (LPARAM)&endPos);
//Initialise the ITextRange with the start and end positions of the selected characters
hr = TextDocument2->lpVtbl->Range(TextDocument2, startPos, endPos, &textRange);
TOM_ShowErrorMessage(hr);
//Initialise the ITextFont object
hr = textRange->lpVtbl->GetFont(textRange, &textFont);
TOM_ShowErrorMessage(hr);
//Make underlining temporary
hr = textFont->lpVtbl->Reset(textFont, tomApplyTmp);
TOM_ShowErrorMessage(hr);
//Prepare to remove temporary underlining from the selected text
hr = textFont->lpVtbl->SetUnderline(textFont, tomNone);
TOM_ShowErrorMessage(hr);
//Remove underlining from the selected text
hr = textFont->lpVtbl->Reset(textFont, tomApplyNow);
TOM_ShowErrorMessage(hr);
//Free the ITextFont object
hr = textFont->lpVtbl->Release(textFont);
TOM_ShowErrorMessage(hr);
//Free the ITextRange object
hr = textRange->lpVtbl->Release(textRange);
TOM_ShowErrorMessage(hr);
}
After selecting the word above the cursor, the ITextFont object is set up by calling the method GetFont.
The Reset() method of this object is called with the argument tomApplyTmp to put the object into its temporary formatting mode.
The parameter tomNone tells the object that we want to remove temporary formatting.
As in the previous code, tomApplyNow is used to implement the removal the temporary formatting.
Finally, the ITextFont object and the ITextRange object are released by calling the appropriate Release() methods.
Using the program in the zip file attached to the first post in this threadThe attached zip file includes a module TomFunctions.c that contains the functions described above. It also includes a module, Main.c, that produces a window containing a rich text edit control and a menu. Type (or paste) some text into this control and then use the menu to apply each of the functions on the text.
ConclusionThe text object model gives access to a number of functions that are not available using the normal Windows API functions. This tutorial could not cover all the functions available but it is hoped that it demonstrates how to use the TOM objects ITextDocument, ITextRange, ITextFont and ITextPara in Pelles C. Delving into the list of TOM constants at
https://learn.microsoft.com/en-us/windows/win32/api/tom/ne-tom-tomconstants will reveal some of the other capabilities that are available using the TOM interface.