[Function] Force Text Wrap GUI

Post your working scripts, libraries and tools for AHK v1.1 and older
DigiDon
Posts: 178
Joined: 19 May 2014, 04:55
Contact:

[Function] Force Text Wrap GUI

28 May 2017, 17:38

Hello all ;) ,

I encountered the problem where you want to display a long string in a fixed-width GUI and the text is not displayed right : parts are missing because the auto-wrap is not behaving properly if you don't have any space in your string.

In my case the sting was a long path and I wanted it to show in center of a GUI box of fixed width. Result was not nice...

So after some searches let me introduce you the solution I found.

I used GetTextSize function from majkinetor to get the Text width before even showing the GUI
https://autohotkey.com/board/topic/1662 ... dimension/
and then the WrapText_Force function I have developed (below).

Suppose you show some long text variable in a GUI with a fixed width of 400 like this:

Code: Select all

Gui, Font, S10 CBlack, Verdana
BottomText:= "D:\EverFastAccess\DEV ENV\4.26\Notebooks\MySpace\Software\Soft_.Notepad++-.D--EverFastAccess-DEV ENV-4.25-Lib-InteractiveTutorials_Fct.ahk - Notepad++.rtf"
Gui, Add, Text, w400 +Center,  %BottomText%
Gui, Show
16/02/2018 : NEW REGEX FUNCTIONS AS ALTERNATIVE
Run this example:

Code: Select all

#NoEnv  ; Recommended for performance and compatibility with future AutoHotkey releases.
; #Warn  ; Enable warnings to assist with detecting common errors.
SendMode Input  ; Recommended for new scripts due to its superior speed and reliability.
SetWorkingDir %A_ScriptDir%  ; Ensures a consistent starting directory.
#SingleInstance, force

msgbox text by width
;The text that might not wrap correclty
BottomText:= "D:\EverFastAccess\DEV ENV\4.26\Notebooks\MySpace\Software\Soft_.Notepad++-.D--EverFastAccess-DEV ENV-4.25-Lib-InteractiveTutorials_Fct.ahk - Notepad++.rtf"
wFont:="S10,Verdana" ; is the intended font ; Pass "" if approx default (size 10 MS Sans Sherif)
wWidth:=410 ;400 is the intended width
Gui, Font, %wFont% ;Set the font
Gui, Add, Text, xm w430 +Center,  % WrapText_ByWidth(BottomText,wWidth,wFont) ;Display the text that will automatically be wrapped
Gui, Show
;*****************************
msgbox test by len
Gui,Destroy
;The text that might not wrap correclty
BottomText:= "D:\EverFastAccess\DEV ENV\4.26\Notebooks\MySpace\Software\Soft_.Notepad++-.D--EverFastAccess-DEV ENV-4.25-Lib-InteractiveTutorials_Fct.ahk - Notepad++.rtf"
Gui, Font, s10 Verdana
Gui, Add, Text, xm w430 +Center, % WrapText_ByLen(BottomText,53) ;53 chars max by lines
Gui, Show
return

Esc::ExitApp

;*****************************
;WrapText_ByWidth <DigiDon>
;WrapText_ByWidth(TextToWrap,P_MaxWidth,P_Font="",P_Delim="[]/\.-_")
;*****************************
WrapText_ByWidth(TextToWrap,P_MaxWidth,P_Font="",P_Delim="[]/\.-_") {
;we round the LengthLim
LengthLim:=Round(LengthLim)
;if P_MaxLen too small or TextToWrap<P_MaxLen we return the original text
;if no specific delimiters were specified we assign the default ones
P_Delim:= RegExReplace(P_Delim, "[\^\-\]\\]", "\$0")
TextSize:=GetTextSize(TextToWrap,P_Font)
; msgbox TextSize %TextSize%
	if !TextSize
		return TextToWrap
TextSplitLen:=Round(P_MaxWidth/TextSize*StrLen(TextToWrap))
; msgbox TextSplitLen %TextSplitLen%
return WrapText_ByLen(TextToWrap,TextSplitLen,P_Delim)
}

;*****************************
;WrapText_ByLen <DigiDon>
;WrapText_ByLen(TextToWrap,LengthLim,P_Delim="[]/\.-_")
;*****************************
WrapText_ByLen(TextToWrap,LengthLim,P_Delim="[]/\.-_") {
;we round the LengthLim
LengthLim:=Round(LengthLim)
;if LengthLim too small or TextToWrap<LengthLim we return the original text
if LengthLim<1
	return TextToWrap
if (StrLen(TextToWrap)<LengthLim)
	return TextToWrap
;if no specific delimiters were specified we assign the default ones
P_RegexDelim:= "[" RegExReplace(P_Delim, "[\[\]\\\/\.\-\_]", "\$0") "]"
CurrPos:=1
TextToWrapNew:=TextToWrap
; msgbox % "text is of size " StrLen(TextToWrapNew))
	While (StrLen(SubStr(TextToWrapNew,CurrPos))>LengthLim) {
		
		; msgbox current pos CurrPos %CurrPos%
		; msgbox % StrLen(SubStr(TextToWrapNew,CurrPos)) " char remaining"
		TempFoundPos:=regexmatch(TextToWrapNew,"\s",,CurrPos)
		FoundPos:=0
		while (TempFoundPos and TempFoundPos<=LengthLim) {
			FoundPos:=TempFoundPos
			CurrPos:=FoundPos+1
			TempFoundPos:=regexmatch(TextToWrapNew,"\s",,CurrPos)
			}
		if (FoundPos) {
			; msgbox found space at pos %FoundPos%
			TextToWrapNew:=SubStr(TextToWrapNew,1,FoundPos-1) "`n" SubStr(TextToWrapNew,FoundPos+1)
			continue
			}
		; msgbox no space found try delim
		TempFoundPos:=regexmatch(TextToWrapNew,P_RegexDelim,,CurrPos)
		FoundPos:=0
		while (TempFoundPos and TempFoundPos<=LengthLim) {
			FoundPos:=TempFoundPos
			CurrPos:=FoundPos+1
			TempFoundPos:=regexmatch(TextToWrapNew,P_RegexDelim,,CurrPos)
		}
		if (FoundPos) {
			; msgbox found delim at pos %FoundPos% LengthLim %LengthLim% CurrPos %CurrPos%
			TextToWrapNew:=SubStr(TextToWrapNew,1,FoundPos) "`n" SubStr(TextToWrapNew,FoundPos+1)
			continue
		}
		; else
		; msgbox % "no delim neither split text at pos " LengthLim
		TextToWrapNew:=SubStr(TextToWrapNew,1,CurrPos+LengthLim) "`n" SubStr(TextToWrapNew,CurrPos+LengthLim+1)
		CurrPos+=LengthLim
	}
	return TextToWrapNew
}

Previous function without regex but with try of smart sizing
New code would be:

Code: Select all

Gui, Font, S10 CBlack, Verdana
BottomText:= "D:\EverFastAccess\DEV ENV\4.26\Notebooks\MySpace\Software\Soft_.Notepad++-.D--EverFastAccess-DEV ENV-4.25-Lib-InteractiveTutorials_Fct.ahk - Notepad++.rtf"
TextSize:=GetTextSize(BottomText, "S10,Verdana", false, 0)
TextSplitLen:=400/TextSize*StrLen(BottomText) ;400 is the intended width
BottomText:=WrapText_Force(BottomText,TextSplitLen)
Gui, Add, Text, w400 +Center,  %BottomText%
Gui, Show
WrapText_Force(TextToWrap,LengthLim,delims="")

Code: Select all

; WrapText_Force
;****************
;By DigiDon
;Returns a new wrapped text variables
;delimiters can be specified in delims array parameters, or will be ["[","/","\",A_Space,A_Tab,".","-","_"] by default
WrapText_Force(TextToWrap,LengthLim,delims="") {
;we round the LengthLim
LengthLim:=Round(LengthLim)
;if LengthLim too small or TextToWrap<LengthLim we return the original text
if LengthLim<1
	return TextToWrap
if (TextToWrap<LengthLim)
	return TextToWrap
;if no specific delimiters were specified we assign the default ones
if !isObject(delims)
		{
		delims:=["[","/","\",A_Space,A_Tab,".","-","_"]
		}
TextToWrap_Arr:=[]
TextWrapedFinal:=""
;the next 2 lines will split TextToWrap along with the current lines it contains
stringreplace,TextToWrap,TextToWrap,`r,,all
StringSplit, TextToWrap_Arr, TextToWrap, `n
;we loop through each lines to wrap them
Loop, %TextToWrap_Arr0%
	{
	;the current line is assigned to TextToWrap
	TextToWrap:=TextToWrap_Arr%A_Index%
	count:=1, TextWraped:="", TextWraped_Arr:=[], LengthLimVar="", Delim_Pos_Arr:=[]
	if (count=1)
		{
		;if the line is already <LengthLim we put it as it is in the TextWrapedFinal string
		if (StrLen(TextToWrap)<LengthLim)
			{
			TextWrapedFinal .= TextWraped . "`n"
			continue
			}
		;otherwise we put it in the TextWraped_Arr
		TextWraped_Arr[count]:=TextToWrap
		}
	;we start wrapping
	WrapText_Force_Recurse:
	loop 1
		{
		;if the current TextWraped_Arr<LengthLim we build the TextWraped and we break the loop 
		;to append the current wrapped part to the TextWrapedFinal var and continue to next line
		Text_StrLen := StrLen(TextWraped_Arr[count])
		if (Text_StrLen<LengthLim)
			{
			loop % TextWraped_Arr.MaxIndex()
				{
				TextWraped.=TextWraped_Arr[A_Index]
				}
			break
			}
		;Otherwise 
		;[OPTIONAL - can be replaced by] LengthLimVar:=LengthLim
		;We Check if the current part is at least >1.5*LengthLim, otherwise we assign a new LengthLim of LengthLim//2
		if (Text_StrLen<(LengthLim*1.5))
			LengthLimVar:=LengthLim//2
		else
			LengthLimVar:=LengthLim
			
		;we look in the current remaining part the positions of delimiters until LengthLim
		PartToLookDelim:=SubStr(TextWraped_Arr[count], 1, LengthLimVar)
		for index,delim in delims
			{
			Delim_Pos_Arr[index]:=InStr(PartToLookDelim, delim,,0)
			}
		
		;We get the farest delimiter pos into Delim_Pos
		Delim_Pos:=0
		for k,v in Delim_Pos_Arr
			if (v > Delim_Pos)
				Delim_Pos := v
		
		;if no delimiter was found or its position is not close enough to the intended Length (set to 90%)
		if (Delim_Pos=0 or Delim_Pos<(LengthLimVar*0.9))
			{
			;We split the Current part to LengthLimVar and we assign the rest to the next part
			TextWraped_Arr[count+1]:=SubStr(TextWraped_Arr[count], LengthLimVar)
			TextWraped_Arr[count]:=SubStr(TextWraped_Arr[count], 1 , LengthLimVar - 1) . "`n"
			}
		;If a delimiter was found close enough to the intended Length
		Else
			{
			;We split the Current part to LengthLimVar and we assign the rest to the next part
			TextWraped_Arr[count+1]:=SubStr(TextWraped_Arr[count], Delim_Pos+1)
			TextWraped_Arr[count]:=SubStr(TextWraped_Arr[count], 1, Delim_Pos)
			if (SubStr(TextWraped_Arr[count], 0 , 1)!=" ")
				TextWraped_Arr[count].="`n"
			}
		;In both cases we then restart the WrapText_Force_Recurse Label with he next part
		count++
		GoTo WrapText_Force_Recurse
		}
	;When we have finished wrapping a line we add the TextWraped to the TextWrapedFinal, including its original new line char
	TextWrapedFinal .= TextWraped . "`n"
	}
;When we have finished with all lines we can delete the last new line char
if (SubStr(TextWrapedFinal, -1 , 2)="`n")
	TextWrapedFinal:=SubStr(TextWrapedFinal, 1 , -2)
	;And we return the TextWrapedFinal
	return TextWrapedFinal
}
Cheers! :P

PS : For the record for people who would like to go further:
I thought of alternatives and even tried to create a called-once variadic function but it proved too difficult for such a specific case so I decided to stay with this solution.
For those interested alternatives could be a GUI, show, hide and get the position of the text but this has proved hard to do because you have to adjust all the new control sizes manually and in case of several relative-positioned controls, that is getting difficult. We could use a temporary GUI but we would have also to put the same fonts etc. Or creating the GUI, calculating and then recreating but that is quite overkilled IMHO. For not having to specify the font manually one solution could be to get them once created using the GetFont function I found from maul-esel.
https://gist.github.com/maul-esel/3835024
Do as you want but I've warned you :P
Last edited by DigiDon on 16 Feb 2018, 07:40, edited 2 times in total.
EverFastAccess : Take Notes on anything the Fast way: Attach notes, Set reminders & Speed up research in 1 gesture - AHK topic
AHK Dynamic Obfuscator L - Protect your AHK code by Obfuscation - AHK topic
QuickModules for Outlook : Sort Outlook emails very quickly to multiple folders - AHK topic
Coding takes lots of time and efforts. If I have helped you or if you enjoy one of my free projects, please consider a small donation :thumbup:
Sorry I am working hard at the moment at a new job and can't commit on delays of answers & updates
DigiDon
Posts: 178
Joined: 19 May 2014, 04:55
Contact:

Re: Force Text Wrap GUI

16 Feb 2018, 07:39

UPDATE 16/02/2018 : NEW REGEX FUNCTIONS AS ALTERNATIVE
Was my regex excercise ! :D
EverFastAccess : Take Notes on anything the Fast way: Attach notes, Set reminders & Speed up research in 1 gesture - AHK topic
AHK Dynamic Obfuscator L - Protect your AHK code by Obfuscation - AHK topic
QuickModules for Outlook : Sort Outlook emails very quickly to multiple folders - AHK topic
Coding takes lots of time and efforts. If I have helped you or if you enjoy one of my free projects, please consider a small donation :thumbup:
Sorry I am working hard at the moment at a new job and can't commit on delays of answers & updates
robodesign
Posts: 934
Joined: 30 Sep 2017, 03:59
Location: Romania
Contact:

Re: [Function] Force Text Wrap GUI

09 Aug 2020, 11:44

Hello!

None of the provided functions hard-wrapped the texts nicely, when given an entire paragraph...

Therefore , i made my own:

Code: Select all

HardWrapText(TextToWrap, LengthLim) {
   Static RegExDelimiters := "(\||\*|\!|\]|\[|\\|\/|\.|\=|\:|\;|\?|\@|\-|\_|\)|\(|\{|\}|\s)"
   LengthLim := Round(LengthLim)
   if (LengthLim<2)
      return TextToWrap
   if (StrLen(TextToWrap) <= LengthLim + 1)
      return TextToWrap

   thisIndex := hasMatchedRegEx := offsetu := 0
   whereHasMatched := newLinez := ""
   
   Loop, Parse, TextToWrap
   {
       thisIndex++
       newLinez .= A_LoopField
       If (A_LoopField="`r" || A_LoopField="`n")
          thisIndex := hasMatchedRegEx := 0

       If RegExMatch(A_LoopField, RegExDelimiters)
       {
          hasMatchedRegEx := 1
          whereHasMatched := A_Index
       }

       If (thisIndex=LengthLim && hasMatchedRegEx=1)
       {
          newLinez := ST_Insert("`n", newLinez, whereHasMatched + offsetu)
          offsetu++
          thisIndex := hasMatchedRegEx := 0
       } Else If (thisIndex=LengthLim)
       {
          newLinez .= "`n"
          thisIndex := hasMatchedRegEx := 0
       }
   }

   ; ToolTip, % foundPos "==" startPos, , , 2
   return newLinez
}

ST_Insert(insert,input,pos=1) {
; from String Things by Tidbit
  Length := StrLen(input)
  ((pos > 0) ? (pos2 := pos - 1) : (((pos = 0) ? (pos2 := StrLen(input),Length := 0) : (pos2 := pos))))
  output := SubStr(input, 1, pos2) . insert . SubStr(input, pos, Length)
  If (StrLen(output) > StrLen(input) + StrLen(insert))
     ((Abs(pos) <= StrLen(input)/2) ? (output := SubStr(output, 1, pos2 - 1) . SubStr(output, pos + 1, StrLen(input)))
     : (output := SubStr(output, 1, pos2 - StrLen(insert) - 2) . SubStr(output, pos - StrLen(insert), StrLen(input))))
  Return output
}
Best regards, Marius.
-------------------------
KeyPress OSD v4: GitHub or forum. (presentation video)
Quick Picto Viewer: GitHub or forum.
AHK GDI+ expanded / compilation library (on GitHub)
My home page.

Return to “Scripts and Functions (v1)”

Who is online

Users browsing this forum: No registered users and 113 guests