News:

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

Main Menu

Implementation of CENTER_ELLIPSIS

Started by NoCforMe, June 15, 2024, 07:26:54 AM

Previous topic - Next topic

NoCforMe

My editor (EdAsm, described elsewhere here) has a status bar, and one of the things displayed there is the result of a search that fails to find the searchee. I noticed that if the search term was long that the display was cut off at the end, which distressed me. So I decided to try to "ellipsis"-ize it in the middle so that you'd see something like

Text ("<very long ... search term>") not found.

How hard could that be? I figured I could get a DC to the status bar window, set a RECT to the part of the status bar I wanted to display in (my status bar is divided into 4 parts), then use DrawText() with the DT_PATH_ELLIPSIS flag to make it fit with an ellipsis in the middle.

Well, it doesn't work. Even though the Micro$oft docs don't specifically say so, this flag only works if the text is an actual pathname, not any arbitrary text (it apparently has to contain at least one '\' character). So scratch that.

I decided to roll my own CENTER_ELLIPSIS routine, here below. It was kinda fun to write. It works thusly:

Since I have a 4-part status bar, I first get the width of the selected part of the bar that the caller wants to display text in, by going through the list of part offsets (StatusParts). Then I get a DC to the status bar window, select the status bar's font into it, then measure the text using GetTextExtentPoint32(). If the text fits, then I simply output it the normal way (using the SB_SETTEXT message).

If it doesn't fit, that's when the fun starts. I have 3 buffers: a "begin" buffer, an "end" buffer, and an "output" buffer. First I prime the output buffer with the ellipsis string ("..."). I go through the text passed in character by character, alternately pulling a character from the beginning of the text and one from the end and placing them in their respective buffers. I also add the characters to the output buffer. At each character I measure the growing string in the output buffer. As soon as it exceeds the width of the status bar segment, I stop, back up by 1 character (whichever one was added last, beginning or end, by using a "whichFlag"), then I output the ellipsis-ized text.

To output the text, I first copy all the "begin" characters to the output buffer, then the ellipsis string, then the "end" characters. Since the end characters are put in the buffer backwards, I have to start at the end of the buffer and work my way backwards.

Anyhow, it works "a treat", as they say. This could easily be adapted to work with any control that displays text (static, edit, etc.), or indeed with any text buffer. (It would be even easier to code with a buffer, since you'd only be dealing with numbers of characters and not their widths.)

;==============================================================
; PutTextWithCenterEllipsis (textPtr, statusSeg)
;
; Displays status text in selected segment.
; Truncates text from both ends if it's too long to fit,
; with an ellipsis (...) in the middle.
;
; This is for a status bar that has been divided into 4 parts.
; It can be adapted for use with any control that displays text
; (static, edit, etc.).
;==============================================================

; Requires the following items:
$numStatusParts EQU 4
$SBlastPartOffset EQU 800
$statusTextOffset EQU 20
StatusParts DD 75, 600, $SBlastPartOffset, -1

PutTextWithCenterEllipsis PROC textPtr:DWORD, statusSeg:DWORD
LOCAL hDC:HDC, sbWidth:DWORD, beginPtr:DWORD, beginCount:DWORD, endPtr:DWORD, endCount:DWORD
LOCAL totalLen:DWORD, beginBuffPtr:DWORD, endBuffPtr:DWORD, whichFlag:DWORD, measurePtr:DWORD
LOCAL gpRect:RECT, outputBuffer[512]:BYTE, beginBuffer[256]:BYTE, endBuffer[256]:BYTE, sizer:SIZEL

; Set rect. to size of selected segment of status bar:
INVOKE GetClientRect, StatusHandle, ADDR gpRect
MOV EAX, gpRect.right
MOV sbWidth, EAX

MOV EAX, statusSeg
TEST EAX, EAX
JZ seg1
CMP EAX, $numStatusParts - 1
JE segN
MOV EDX, EAX
MOV ECX, EAX
DEC ECX
MOV EAX, [EDX * 4 + StatusParts] ;X-position of this status bar segment.
SUB EAX, [ECX * 4 + StatusParts] ;X-position of previous segment.
JMP SHORT setseg

segN: MOV EAX, sbWidth
MOV EDX, statusSeg
SUB EAX, [EDX * 4 + StatusParts]
JMP SHORT setseg

seg1: MOV EAX, StatusParts ;1st segment width.
setseg: SUB EAX, $statusTextOffset ;Tweak.
MOV gpRect.right, EAX
MOV gpRect.left, 0

; Get & set up DC for measuring text in status bar:
INVOKE GetDC, StatusHandle
MOV hDC, EAX
INVOKE SendMessage, StatusHandle, WM_GETFONT, 0, 0
INVOKE SelectObject, hDC, EAX
INVOKE SetTextAlign, hDC, TA_LEFT or TA_TOP or TA_NOUPDATECP

; See if text needs to be truncated:
MOV EAX, textPtr
CALL strlen
MOV totalLen, EAX
INVOKE GetTextExtentPoint32, hDC, textPtr, totalLen, ADDR sizer
MOV EAX, sizer.x
CMP EAX, gpRect.right
JA trunc
MOV EAX, textPtr
JMP puttxt

; Text needs to be truncated:
trunc: LEA EAX, beginBuffer
MOV beginBuffPtr, EAX
LEA EAX, endBuffer
MOV endBuffPtr, EAX
MOV EAX, textPtr
MOV beginPtr, EAX
ADD EAX, totalLen
DEC EAX
MOV endPtr, EAX
INVOKE strcpy, OFFSET EllipsisStr, ADDR outputBuffer
LEA EAX, outputBuffer
ADD EAX, EDX ;+ len of text just copied.
MOV measurePtr, EAX ;Use output buffer for measurement.
MOV beginCount, 0
MOV endCount, 0
MOV whichFlag, 0
MOV totalLen, 0

;***** Loop through text to measure length for ellipsis-ized result: *****
; Take characters alternately from beginning & end of text and
; add to measurement buffer until length exceeds width of status segment
; (using GetTextExtentPoint32() to measure text):
tloop: MOV EDX, beginPtr
MOV AL, [EDX]
MOV EDX, beginBuffPtr
MOV [EDX], AL
MOV EDX, measurePtr
MOV [EDX], AL
INC measurePtr
INC beginPtr
INC beginBuffPtr
INC beginCount
INC totalLen
INVOKE GetTextExtentPoint32, hDC, ADDR outputBuffer, totalLen, ADDR sizer
MOV EAX, sizer.x
CMP EAX, gpRect.right
JA toobig
XOR whichFlag, 1 ;Toggle flag between begin/end buffers.
MOV EDX, endPtr
MOV AL, [EDX]
MOV EDX, endBuffPtr
MOV [EDX], AL
MOV EDX, measurePtr
MOV [EDX], AL
INC measurePtr
DEC endPtr
INC endBuffPtr
INC endCount
INC totalLen
INVOKE GetTextExtentPoint32, hDC, ADDR outputBuffer, totalLen, ADDR sizer
MOV EAX, sizer.x
CMP EAX, gpRect.right
JA toobig
XOR whichFlag, 1 ;Toggle flag back.
JMP tloop

; Text has just exceeded width of status segment, so back it up by 1 char:
toobig: CMP whichFlag, 1 ;Were we on beginning or end?
JE @F
DEC beginCount
JMP SHORT output

@@: DEC endCount

; Output truncated text:
output: INVOKE _strncpy, ADDR beginBuffer, ADDR outputBuffer, beginCount
LEA EAX, outputBuffer
INVOKE _strcat, OFFSET EllipsisStr, ADDR outputBuffer

; End string must be copied in reverse:
PUSH ESI
PUSH EDI
LEA EAX, outputBuffer
MOV EDI, EAX
CALL strlen
ADD EDI, EAX ;Dest. = end of outputBuffer (so far).

LEA EAX, endBuffer
ADD EAX, endCount
DEC EAX
MOV ESI, EAX ;Src. = end of endBuffer.

MOV ECX, endCount
JECXZ @F ;Just in case end count = 0.

eloop: MOV AL, [ESI]
DEC ESI
STOSB
LOOP eloop
MOV BYTE PTR [EDI], 0 ;Terminate output buffer.

@@: POP EDI
POP ESI
LEA EAX, outputBuffer

puttxt: INVOKE SendMessage, StatusHandle, SB_SETTEXT, statusSeg, EAX
INVOKE ReleaseDC, StatusHandle, hDC
RET

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

sinsi

Just curious, did you try DT_END_ELLIPSIS or DT_WORD_ELLIPSIS?

NoCforMe

I did try DT_END_ELLIPSIS and it worked as advertised, but wasn't what I wanted here. Didn't try the other one.
Assembly language programming should be fun. That's why I do it.