News:

Masm32 SDK description, downloads and other helpful links
Message to All Guests
NB: Posting URL's See here: Posted URL Change

Main Menu

Painting a RichEdit control

Started by NoCforMe, February 18, 2024, 09:41:08 AM

Previous topic - Next topic

NoCforMe

This little project ended up being a quite complex and roundabout way to perform a very simple task. Since it ultimately worked, I'm presenting it here.

Task: to highlight a single line of text in a RichEdit control (without actually selecting the text).

Background: This is for my code editor, EdAsm, which has a "goto line" function. This function worked, but was hard to use since the only clue to which line was gone to was the flashing caret, sometimes very hard to see. So I decided I wanted to highlight the entire line to make it obvious.

I knew I was going to have to do some messing around with DCs (device contexts) and such. It took about three days of trying this and trying that to come up with a working scheme. Here's what I ended up with.

  • Set the text position in the control:
    This is done by the existing "goto line #" code.
  • Get the (x,y) position of the text:
    I use EM_GETSEL and EM_POSFROMCHAR to give me the (x,y) pos. in device units.
  • Create a RECT the size of the line of text:
    The RECT is the width of the control (from GetClientRect()) and the height of a single line of text, from a pre-calculated global variable, EditLineHeight.
  • Create 2 DCs:
    The usual two: one from the source (the edit control) using GetDC(), the other a memory DC for the bitmap I'm about to create using CreateCompatibleDC().
  • Create the bitmap, using CreateCompatibleBitmap().
  • Copy the image of the line of text into the memory DC, using BitBlt().
  • Get the bitmap info using GetObject().
  • Create a BITMAPINFO structure from that info.
  • Get the image of the line of text into my bitmap buffer using GetDIBits().
  • Go through the bitmap bits in my buffer, changing any white (background) pixels to my selected background color.
  • Copy the altered bitmap bits back into the bitmap buffer, using SetDIBits().
  • Copy the bitmap bits into the edit control DC using BitBlt().

Yikes; that's a lot of code for such a simple task. But it works!

Couple of notes:
  • I make a major assumption in this code: that the color depth of the images I'm working with is 32 bits/pixel (that's what I get back from the RichEdit DC). I think this is a pretty safe assumption, but a careful programmer might want to check this. It turns out that this would also cover the case (unlikely but possible) of 24 bits/pixel, which is basically the same thing: I'm only dealing with RGB colors here and just zero out the high high byte (possible alpha channel), which in any case turns out always to be zero anyhow.
  • I used my usual colors to do the highlighting but was surprised to find out that they appeared to be complements of the color I wanted. WTF? Turns out that the color must be defined as BGR, not RGB (different byte order).

The color highlighting is totally benign, doesn't affect anything else in the RichEdit control, and is temporary; it goes away either if you scroll the line out of sight or if you select any text. Just what the doctor ordered.

Here's the routine that does all this (minus the "goto line #" code and the code that sets EditLineHeight):
;==================================================================
; HighlightLine()
;
; Creates a temporary highlight of the line of text selected by the
; "goto line" function.
;==================================================================

HighlightLine PROC
LOCAL hDC:HDC, memDC:HDC, compBMP:HBITMAP, x:DWORD, y:DWORD, startPos:DWORD
LOCAL bi:BITMAPINFO, bm:BITMAP, bmpBuffer:DWORD, gpRect:RECT

; Allocate memory buffer for bitmap bits:
INVOKE HeapAlloc, HeapHandle, 0, 64 * 1024
MOV bmpBuffer, EAX

; Get position of text selection:
INVOKE SendMessage, EditHandle, EM_GETSEL, ADDR startPos, NULL
INVOKE SendMessage, EditHandle, EM_POSFROMCHAR, startPos, 0

; (x,y) pos. comes packed into a DWORD:
MOVZX ECX, AX ;X-pos.
MOV x, ECX
SHR EAX, 16 ;Y-pos.
MOV y, EAX

; Set rectangle to the selected line:
INVOKE GetClientRect, EditHandle, ADDR gpRect
MOV gpRect.left, 0
MOV EAX, y
MOV gpRect.top, EAX
ADD EAX, EditLineHeight
MOV gpRect.bottom, EAX

; Create 2 DCs & a bitmap the size of 1 line of text:
INVOKE GetDC, EditHandle
MOV hDC, EAX
INVOKE CreateCompatibleDC, hDC
MOV memDC, EAX
INVOKE CreateCompatibleBitmap, hDC, gpRect.right, EditLineHeight
MOV compBMP, EAX
INVOKE SelectObject, memDC, compBMP

; Get the line of text into our memory DC:
INVOKE BitBlt, memDC, 0, 0, gpRect.right, EditLineHeight, hDC, $editLmargin, gpRect.top, SRCCOPY

; Fill a BITMAPINFO structure from the bitmap:
INVOKE GetObject, compBMP, SIZEOF bm, ADDR bm
MOV bi.bmiHeader.biSize, SIZEOF BITMAPINFOHEADER
MOV EAX, bm.bmWidth
MOV bi.bmiHeader.biWidth, EAX
MOV EAX, bm.bmHeight
MOV bi.bmiHeader.biHeight, EAX
MOV AX, bm.bmPlanes
MOV bi.bmiHeader.biPlanes, AX
MOV AX, bm.bmBitsPixel
MOV bi.bmiHeader.biBitCount, AX
MOV bi.bmiHeader.biCompression, 0
MOV bi.bmiHeader.biSizeImage, 0
MOV bi.bmiHeader.biXPelsPerMeter, 0
MOV bi.bmiHeader.biYPelsPerMeter, 0
MOV bi.bmiHeader.biClrUsed, 0
MOV bi.bmiHeader.biClrImportant, 0

; Get the bitmap bits out of the memory DC:
INVOKE GetDIBits, memDC, compBMP, 0, EditLineHeight, bmpBuffer, ADDR bi, DIB_RGB_COLORS

; Figure out color depth of bitmap. ASSUMPTION here: it's 32bpp. Probably safe to assume.
MOV EAX, bm.bmWidth
MUL bm.bmHeight
MOV ECX, EAX
MOV EDX, bmpBuffer

; Go through pixel buffer, change white to our highlight background color:
bgloop: MOV EAX, [EDX]
AND EAX, 0FFFFFFh ;Strip out any possible alpha channel.
CMP EAX, 0FFFFFFh ;Is pixel white?
JNE @F ;  Nope.

; Turns out we need a BGR color here, not RGB:
MOV DWORD PTR [EDX], $colorGotoHlBGR ;  Yep, so change it to highlight color.
@@: ADD EDX, SIZEOF DWORD
LOOP bgloop

; Copy pixels back into the memory DC:
INVOKE SetDIBits, memDC, compBMP, 0, EditLineHeight, bmpBuffer, ADDR bi, DIB_RGB_COLORS
INVOKE BitBlt, hDC, $editLmargin, gpRect.top, gpRect.right, EditLineHeight, memDC, 0, 0, SRCCOPY

; Clean up:
INVOKE ReleaseDC, EditHandle, hDC
INVOKE DeleteDC, memDC
INVOKE DeleteObject, compBMP
INVOKE HeapFree, HeapHandle, 0, bmpBuffer

RET

HighlightLine ENDP
Assembly language programming should be fun. That's why I do it.

NoCforMe

BTW, since someone is going to object and say "Why didn't you just select the line to highlight it?", let me explain: I used to do things this way, but I didn't like the result. Too easy to hit a key and wipe out the entire line.

I also tried just inverting the image of the line by using SRCINVERT in the second BitBlt() instead of just SRCCOPY, which would have avoided all that nonsense with having to create a bitmap, but the result of that was just plain ugly. So that's how I ended up with this code.
Assembly language programming should be fun. That's why I do it.

jj2007

Quote from: NoCforMe on February 18, 2024, 09:45:51 AM"Why didn't you just select the line to highlight it?"

Yep, I was going to ask that. Ever checked the behaviour of bookmarks in RichMasm?



I never had problems with hitting unwantingly a key and erasing the selection. However, I definitely want to see the text that corresponds to the clicked bookmark. Seeing the entire line would be confusing.

P.S.: Compliments for your solution. Not my taste, but you did a really good job!

NoCforMe

Quote from: jj2007 on February 18, 2024, 11:03:39 AMP.S.: Compliments for your solution. Not my taste, but you did a really good job!
Thanks. Coming from you that means something.

My reason for posting this was not so much to show my specific usage as to put it out there as a general technique for anyone wanting to paint on a RichEdit control. You could certainly use it to highlight selected words or other text, or for any other purpose.
Assembly language programming should be fun. That's why I do it.

lingo

Can you use this technique for all rows in the currently visible page of the Rich Edit Control
to highlight individual words from the lines (Syntax highlighting)
without usage of EM_SETCHARFORMAT  :thumbsup:
Quid sit futurum cras fuge quaerere.

NoCforMe

#5
I believe so, yes; remember, this technique only uses EM_xxx messages to get (x,y) positioning (and not even RichEdit messages at that). (You'd have to use EM_FINDTEXTEX to find the words.) But keep in mind that the highlighting is temporary, not permanent. I suppose you could refresh it manually.

However, there are better ways to do what you want, but they'd probably require using EM_SETCHARFORMAT (not sure why you don't want to use this). Plus your text would have to be in Rich Text format (RTF), not plain ASCII.

JJ would probably be the one to give you the lowdown on this stuff.
Assembly language programming should be fun. That's why I do it.

jj2007

Quote from: NoCforMe on February 18, 2024, 12:48:47 PMJJ would probably be the one to give you the lowdown on this stuff.

- Iczelion Tutorial 35: RichEdit Control: Syntax Hilighting
- Iczelion tutorial 35 Highlighting

I used automatic syntax highlighting 30 years ago with GfaBasic, and it was ok. For Assembly, which is much more complex, I prefer to highlight my tricks manually, like this:


Excerpt from a 48,000 lines source. Even years later, there is a chance that I understand what I was doing :cool:

TimoVJL

There are many richedit highlighters.
I used one with VbsEdit / VxEdit editors for scripts language.
Those are of course a bit slow, but users wanted highlighting for an odd CAD with a strange script language.
CAD company said, that feature isn't important for users, so have to make own.
What i learned long time ago, ask from women coworkers, what can help casual working.
That happened around 2007, so don't ask details.
May the source be with you

lingo

#8
With EM_SETCHARFORMAT it works fine but has problems with:
- Undo function
- to not save the highlighted content in a file; only the original colors
- etc.
The "richedit" highlighters work only in text mode, not in RTF mode,
where one word can have different symbols by sizes, by colors, by fonts, etc.
This could be a problem in your technique as well
because it is not clear how to recognize which  color of the symbol is
foreground and which is background.

QuoteJJ would probably be the one to give you the lowdown on this stuff.

QuoteThe text has been removed. It does not relate to the content of the thread and is written in an offensive manner.
Biterider
Quid sit futurum cras fuge quaerere.

NoCforMe

Quote from: lingo on February 19, 2024, 02:33:10 AMThis could be a problem in your technique as well
because it is not clear how to recognize which  color of the symbol is
foreground and which is background.
Well, my technique really wouldn't be suitable for syntax highlighting as you're describing. It's temporary and would be far too cumbersome for that purpose.
Assembly language programming should be fun. That's why I do it.

jj2007

Quote from: NoCforMe on February 19, 2024, 09:18:37 AMWell, my technique really wouldn't be suitable for syntax highlighting as you're describing.

There is a third option half way between Iczelion-style automatic highlighting and RichMasm-style manual formatting:
- on load, go through the whole text
- use EM_SETCHARFORMAT for all keywords found
- save either as rtf (keeping the formatting) or as plain text

You can see that in action when opening a plain text *.asm source in RichMasm. It might be a bit slow for bigger sources, but for 1000 lines source it's just milliseconds.

NoCforMe

Helpful, but: could you explain this in non-RichMasm terms, for those few of us who don't happen to use that software? Maybe some plain assembly examples?
Assembly language programming should be fun. That's why I do it.

jj2007

Quote from: NoCforMe on February 19, 2024, 10:46:07 AMcould you explain this in non-RichMasm terms

- for the manual formatting, see the image in reply #6
- automatic syntax highlighting is your own approach

In between is a technique where you parse the text exactly as for automatic syntax highlighting, but instead of wm-painting over the RichEdit control, you use EM_SETCHARFORMAT to format the text permanently for all keywords found:

.code
MyTest proc arg1, arg2
  ret
MyTest endp

start:
  push 123
  push 456
  call MyTest

It is somewhat less complicated to code, and will look nicer.

NoCforMe

Quote from: jj2007 on February 19, 2024, 12:16:39 PM
Quote from: NoCforMe on February 19, 2024, 10:46:07 AMcould you explain this in non-RichMasm terms

- for the manual formatting, see the image in reply #6
Looked at it; don't know what MbUTF8() is or does (something to do w/UTF-8, obviously, but what?). Also not familiar w/Scintilla (I know what it is), no idea what exactly SCI_SETTEXTdoes.
Assembly language programming should be fun. That's why I do it.