Email Address Validation

Share your advanced PureBasic knowledge/code with the community.
IdeasVacuum
Always Here
Always Here
Posts: 6426
Joined: Fri Oct 23, 2009 2:33 am
Location: Wales, UK
Contact:

Email Address Validation

Post by IdeasVacuum »

Email address validation is usually solved via regex. If your app needs to plough through a lot of emails then regex is probably the only viable answer. However, if your app is collecting a single email address from a potential customer, you want more than something that offers accept/reject. I recently found myself staring at a rejected email address and not knowing why - what was wrong? I often use that address? Doesn't the app vendor want my money?

Now the world of the email address is surprisingly treacherous. There are some good standards, but many if not most mail servers do not fully comply with them. So, when your app tests an email address, the test should be to see if your mail server can handle it.

The code below (no regex!) is Windows only because of it's use of RTF in an editor gadget. It assumes that at run time your app will collect an email address, Trim() any spaces, then pass the address on for checking. Modify the permitted chars accordingly to suit your mail server - most will only allow a few 'special' chars.

Code: Select all

;Reference: https://en.wikipedia.org/wiki/Email_address#Internationalization

;The transmission of electronic mail within the Internet uses the Simple Mail Transfer Protocol (SMTP),
;defined in Internet standards RFC 5321/RFC 5322 and extensions like RFC 6531

;The 'rules' below ignore Internationalization. If your mail server is compliant with RFC6530, then UTF8 chars
;are acceptable and email addresses can be in any language. You would then need lists of 'not permitted chars'
;rather than lists of 'permitted chars'. However, not even Google GMail or Microsoft Outlook allow non-ASCII
;email addresses to be registered, to date (2016-09-03).

;RULES  !***! = Consider avoiding as not supported universally
;Modify these rules to suit your mail server

;1) must have '@'
;2) must have only 1 '@'
;3) no leading or trailing spaces

;4)  Local Part can use:
;4a) Ascii A to Z and a to z
;4b) Digits 0 to 9
;4c) Special chars !#$%&'*+-/=?^_`{|}~  !***!
;4d) Dot '.'
;4e) Comments in brackets (comment)  !***!
;4f) Max length 64 chars
;4g) International chars encoded as UTF8, re RFC 6531  !***!
;4h) Quoted string format allowed  !***!
;4i) Spaces  !***!

;5)  Local Part cannot use:
;5a) Dot '.' as first or last char (unless in a quoted string)
;5b) Consecutive dots '..' '...' etc (unless in a quoted string)

;6)  Domain Part can use:
;6a) Ascii A to Z and a to z
;6b) Digits 0 to 9
;6c) Hyphen '-'
;6d) Literal IP Address in square brackets e.g. [192.168.2.1] or [IPv6:2001:db8::1]  !***!
;6e) Comments in brackets (comment)  !***!
;6f) Max total length = (254 - Local Part Length)
;6g) Max length of each dot-seperated part = 63

;7)  Domain Part cannot use:
;7a) Hyphen '-' as first or last char
;7b) Consecutive dots '..' '...' etc


#MaxEmailLocalPartLen = 64
#MaxEmailDomainSubLen = 63
#MaxEmailWholeLength  = 254

Enumeration
#WinEmailInvalid
#LstEmailInvalid
#EdEmailInvalid
#BtnEmailInvalid
#Font08R
#Font10B
#Font12R
EndEnumeration

Structure CharPos
iStartChar.i
iEndChar.i
iLstItem.i
EndStructure

Global NewList gCharPos.CharPos()

XIncludeFile "..\Editor Rtf Functions.pbi"

Global  igDpi.i = GetDeviceCaps_(GetDC_(0),#LOGPIXELSX)
Global igFS08.i = (( 8 * 100) / igDpi)
Global igFS10.i = ((10 * 100) / igDpi)
Global igFS12.i = ((12 * 100) / igDpi)

LoadFont(#Font08R, "Microsoft Sans Serif", igFS08, #PB_Font_HighQuality)
LoadFont(#Font10B, "Microsoft Sans Serif", igFS10, #PB_Font_HighQuality | #PB_Font_Bold)
LoadFont(#Font12R, "Microsoft Sans Serif", igFS12, #PB_Font_HighQuality)


Procedure CheckEmailAddress(sEmail.s)
;#-----------------------------------
;Note, the Email address should ideally be passed 'pre trimmed' of leading/trailing spaces
;You don't really want to bother your customer just for that.....

Protected          iLastAtChar.i = CountString(sEmail, Chr(64)) + 1
Protected           sLocalPart.s = StringField(sEmail, 1, Chr(64))
Protected          sDomainPart.s = StringField(sEmail, iLastAtChar, Chr(64))
Protected                sChar.s, sSub.s
Protected     sConsecutiveDots.s = Chr(46) + Chr(46)
Protected   sPermittedDomChars.s = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789[]:-."
Protected sPermittedLocalChars.s = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!#$%&'*+-/=?^_`{|}~."
Protected            iDomStart.i = Len(sLocalPart) + 1
Protected          iFaultFound.i = #False
Protected    iSqrOpenBrktFound.i = 0
Protected   iSqrCloseBrktFound.i = 0
Protected     iExcess.i, iFind.i, iPos.i, iStart.i, iCharPos.i, iLen.i
Protected              iHiLite.i = RGB(128,255,0)

              ClearGadgetItems(#LstEmailInvalid)
                          ClearList(gCharPos())

              ;Permitted local chars check
                                  iLen = Len(sLocalPart)
              For iCharPos = 1 To iLen

                       sChar = Mid(sLocalPart, iCharPos, 1)
                        iPos = FindString(sPermittedLocalChars, sChar, 1, #PB_String_NoCase)
                     If(iPos = 0)

                            iFaultFound = #True
                            AddGadgetItem(#LstEmailInvalid, -1, "disallowed char")
                               AddElement(gCharPos())
                                          gCharPos()\iStartChar = iCharPos
                                          gCharPos()\iEndChar   = iCharPos + 1
                                          gCharPos()\iLstItem   = CountGadgetItems(#LstEmailInvalid) - 1
                     EndIf
              Next

              ;Permitted domain chars check
                                  iLen = Len(sDomainPart)
              For iCharPos = 1 To iLen

                       sChar = Mid(sDomainPart, iCharPos, 1)
                        iPos = FindString(sPermittedDomChars, sChar, 1, #PB_String_NoCase)
                     If(iPos = 0)

                            iFaultFound = #True
                            AddGadgetItem(#LstEmailInvalid, -1, "disallowed char")
                               AddElement(gCharPos())
                                          gCharPos()\iStartChar = iCharPos + iDomStart
                                          gCharPos()\iEndChar   = iCharPos + iDomStart + 1
                                          gCharPos()\iLstItem   = CountGadgetItems(#LstEmailInvalid) - 1
                     EndIf

                     If(Asc(sChar) = 91) :  iSqrOpenBrktFound = iCharPos + iDomStart : EndIf ;[
                     If(Asc(sChar) = 93) : iSqrCloseBrktFound = iCharPos + iDomStart : EndIf ;]
              Next

              If FindString(sPermittedDomChars, Chr(91), 1, #PB_String_NoCase) ;[

                        If((iSqrOpenBrktFound > 0) And (iSqrCloseBrktFound = 0))

                                      AddGadgetItem(#LstEmailInvalid, -1, "missing bracket ]")
                                         AddElement(gCharPos())
                                                    gCharPos()\iStartChar = iSqrOpenBrktFound
                                                    gCharPos()\iEndChar   = iSqrOpenBrktFound + 1
                                                    gCharPos()\iLstItem   = CountGadgetItems(#LstEmailInvalid) - 1
                        EndIf

                        If((iSqrOpenBrktFound = 0) And (iSqrCloseBrktFound > 0))

                                      AddGadgetItem(#LstEmailInvalid, -1, "missing bracket [")
                                         AddElement(gCharPos())
                                                    gCharPos()\iStartChar = iSqrCloseBrktFound
                                                    gCharPos()\iEndChar   = iSqrCloseBrktFound + 1
                                                    gCharPos()\iLstItem   = CountGadgetItems(#LstEmailInvalid) - 1
                        EndIf
              EndIf

                 iFound = CountString(sEmail, Chr(64)) ;@
              If(iFound > 1)

                          iFaultFound = #True
                                 iPos = -1

                          For iCnt = 1 To iFound

                                    iPos = FindString(sEmail, Chr(64), iPos + 1, #PB_String_NoCase)

                                        AddGadgetItem(#LstEmailInvalid, -1, "too many '@'")
                                           AddElement(gCharPos())
                                                      gCharPos()\iStartChar = iPos
                                                      gCharPos()\iEndChar   = iPos + 1
                                                      gCharPos()\iLstItem   = CountGadgetItems(#LstEmailInvalid) - 1
                          Next

              ElseIf(iFound = 0) : iFaultFound = #True : AddGadgetItem(#LstEmailInvalid, -1, "missing '@'") : EndIf

                 iFound = CountString(sEmail, Chr(32)) ;space
              If(iFound > 0)

                          iFaultFound = #True
                                 iPos = -1

                          For iCnt = 1 To iFound

                                    iPos = FindString(sEmail, Chr(32), iPos + 1, #PB_String_NoCase)

                                        AddGadgetItem(#LstEmailInvalid, -1, "space disallowed")
                                           AddElement(gCharPos())
                                                      gCharPos()\iStartChar = iPos
                                                      gCharPos()\iEndChar   = iPos + 1
                                                      gCharPos()\iLstItem   = CountGadgetItems(#LstEmailInvalid) - 1
                          Next
              EndIf

                 iFound = CountString(sEmail, sConsecutiveDots) ;consecutive dots
              If(iFound > 0)

                          iFaultFound = #True
                                 iPos = -1

                          For iCnt = 1 To iFound

                                    iPos = FindString(sEmail, sConsecutiveDots, iPos + 1, #PB_String_NoCase)

                                        AddGadgetItem(#LstEmailInvalid, -1, "consecutive dots disallowed")
                                           AddElement(gCharPos())
                                                      gCharPos()\iStartChar = iPos
                                                      gCharPos()\iEndChar   = iPos + 2
                                                      gCharPos()\iLstItem   = CountGadgetItems(#LstEmailInvalid) - 1
                          Next
              EndIf

                 sChar = Left(sEmail, 1) ;dot prefix?
              If(sChar = Chr(46))

                          iFaultFound = #True
                                 iPos = 1

                          AddGadgetItem(#LstEmailInvalid, -1, "dot prefix disallowed")
                             AddElement(gCharPos())
                                        gCharPos()\iStartChar = iPos
                                        gCharPos()\iEndChar   = iPos + 1
                                        gCharPos()\iLstItem   = CountGadgetItems(#LstEmailInvalid) - 1

              EndIf

                 sChar = Right(sEmail, 1) ;dot postfix?
              If(sChar = Chr(46))

                          iFaultFound = #True
                                 iPos = Len(sEmail)

                          AddGadgetItem(#LstEmailInvalid, -1, "dot postfix disallowed")
                             AddElement(gCharPos())
                                        gCharPos()\iStartChar = iPos
                                        gCharPos()\iEndChar   = iPos + 1
                                        gCharPos()\iLstItem   = CountGadgetItems(#LstEmailInvalid) - 1
              EndIf

                 sChar = Right(sLocalPart, 1) ;dot postfix?
              If(sChar = Chr(46))

                          iFaultFound = #True
                                 iPos = Len(sLocalPart)

                          AddGadgetItem(#LstEmailInvalid, -1, "dot postfix disallowed")
                             AddElement(gCharPos())
                                        gCharPos()\iStartChar = iPos
                                        gCharPos()\iEndChar   = iPos + 1
                                        gCharPos()\iLstItem   = CountGadgetItems(#LstEmailInvalid) - 1
              EndIf

              If(Len(sLocalPart) > #MaxEmailLocalPartLen)

                          iFaultFound = #True
                              iExcess = Len(sLocalPart) - #MaxEmailLocalPartLen

                              AddGadgetItem(#LstEmailInvalid, -1, sLocalPart + " too long by " + Str(iExcess))
                                 AddElement(gCharPos())
                                            gCharPos()\iStartChar = #MaxEmailLocalPartLen
                                            gCharPos()\iEndChar   = #MaxEmailLocalPartLen + iExcess
                                            gCharPos()\iLstItem   = CountGadgetItems(#LstEmailInvalid) - 1
              EndIf

              If(Len(sEmail) > #MaxEmailWholeLength)

                          iFaultFound = #True
                              iExcess = Len(sEmail) - #MaxEmailWholeLength
                              AddGadgetItem(#LstEmailInvalid, -1, "address too long by " + Str(iExcess))
                                 AddElement(gCharPos())
                                            gCharPos()\iStartChar = #MaxEmailWholeLength
                                            gCharPos()\iEndChar   = #MaxEmailWholeLength + iExcess
                                            gCharPos()\iLstItem   = CountGadgetItems(#LstEmailInvalid) - 1
              EndIf

                 iFound = CountString(sDomainPart, Chr(46)) ;dot
              If(iFound > 1)

                          iSubStart = 0

                          For iCnt = 1 To iFound

                                           sSub = StringField(sDomainPart, iCnt, Chr(46))
                                    If(Len(sSub) > #MaxEmailDomainSubLen)

                                           iFaultFound = #True
                                               iExcess = Len(sSub) - #MaxEmailDomainSubLen
                                               AddGadgetItem(#LstEmailInvalid, -1, "Domain Sub too long by " + Str(iExcess))
                                                  AddElement(gCharPos())
                                                             gCharPos()\iStartChar = iSubStart + #MaxEmailDomainSubLen
                                                             gCharPos()\iEndChar   = iSubStart + #MaxEmailDomainSubLen + iExcess
                                                             gCharPos()\iLstItem   = CountGadgetItems(#LstEmailInvalid) - 1
                                    EndIf

                                    iSubStart = Len(sSub) + 1
                          Next
              EndIf

                 sChar = Left(sDomainPart, 1) ;hyphen prefix?
              If(sChar = Chr(45))

                          iFaultFound = #True

                          AddGadgetItem(#LstEmailInvalid, -1, "hyphen prefix disallowed")
                             AddElement(gCharPos())
                                        gCharPos()\iStartChar = iDomStart + 1
                                        gCharPos()\iEndChar   = iDomStart + 2
                                        gCharPos()\iLstItem   = CountGadgetItems(#LstEmailInvalid) - 1

              EndIf

                 sChar = Right(sDomainPart, 1) ;hyphen postfix?
              If(sChar = Chr(45))

                          iFaultFound = #True
                                 iPos = Len(sEmail)

                          AddGadgetItem(#LstEmailInvalid, -1, "hyphen postfix disallowed")
                             AddElement(gCharPos())
                                        gCharPos()\iStartChar = iPos
                                        gCharPos()\iEndChar   = iPos + 1
                                        gCharPos()\iLstItem   = CountGadgetItems(#LstEmailInvalid) - 1
              EndIf

              ;Highlight all faults
              If(iFaultFound = #True)

                          SetGadgetText(#EdEmailInvalid, sEmail)

                          ForEach gCharPos()

                                      Editor_Select(#EdEmailInvalid, 0, gCharPos()\iStartChar, 0, gCharPos()\iEndChar)
                                  Editor_BackColour(#EdEmailInvalid, iHiLite)
                          Next

                          Editor_CursorToEot(#EdEmailInvalid) ;Set cursor at end of text

                            HideWindow(#WinEmailInvalid, #False)
                          StickyWindow(#WinEmailInvalid, #True)
              EndIf
EndProcedure

Procedure WinEmailInvalid()
;#-------------------------

              If OpenWindow(#WinEmailInvalid, 0, 0, 568, 58, "Sorry, email address invalid on our mail server", #PB_Window_SystemMenu | #PB_Window_SizeGadget | #PB_Window_Invisible | #PB_Window_ScreenCentered)

                            WindowBounds(#WinEmailInvalid, 568, 58, #PB_Ignore, 58)
                          SetWindowColor(#WinEmailInvalid, 14024661)

                          ListViewGadget(#LstEmailInvalid,   0,  0, 200, 58)
                            EditorGadget(#EdEmailInvalid,  204,  4, 360, 25, #PB_Editor_ReadOnly)
                            ButtonGadget(#BtnEmailInvalid, 204, 32, 360, 25, "OK")

                           SetGadgetFont(#LstEmailInvalid, FontID(#Font08R))
                          SetGadgetColor(#LstEmailInvalid, #PB_Gadget_FrontColor, 3224832)
                          SetGadgetColor(#LstEmailInvalid, #PB_Gadget_BackColor, 14024661)

                           SetGadgetFont(#EdEmailInvalid, FontID(#Font12R))
                          SetGadgetColor(#EdEmailInvalid, #PB_Gadget_FrontColor, 3224832)
                      Editor_JustifyPara(#EdEmailInvalid, #PFA_CENTER)

                           SetGadgetFont(#BtnEmailInvalid, FontID(#Font10B))
              EndIf
EndProcedure

Procedure EmailInvalidWait()
;#--------------------------
Protected  iEvent.i = 0, iItem.i = -1
Protected   iExit.i = #False
Protected iHiLite.i = RGB(255,55,255)

              Repeat
                            iEvent = WaitWindowEvent(1)
                     Select iEvent

                             Case #PB_Event_CloseWindow

                                          If EventWindow() = #WinEmailInvalid

                                                   HideWindow(#WinEmailInvalid, #True)
                                                 StickyWindow(#WinEmailInvalid, #False)
                                                 iExit = #True
                                          EndIf

                             Case #PB_Event_SizeWindow

                                          If EventWindow() = #WinEmailInvalid

                                                 ResizeGadget(#EdEmailInvalid,  #PB_Ignore, #PB_Ignore, WindowWidth(#WinEmailInvalid) - 208, #PB_Ignore)
                                                 ResizeGadget(#BtnEmailInvalid, #PB_Ignore, #PB_Ignore, WindowWidth(#WinEmailInvalid) - 208, #PB_Ignore)
                                          EndIf

                             Case #PB_Event_Gadget

                                          Select EventGadget()

                                                 Case #BtnEmailInvalid: HideWindow(#WinEmailInvalid, #True) : StickyWindow(#WinEmailInvalid, #False) : iExit = #True
                                                 Case #LstEmailInvalid
                                                                           iItem = GetGadgetState(#LstEmailInvalid)
                                                                        If(iItem > -1)

                                                                                ForEach gCharPos()

                                                                                        If gCharPos()\iLstItem = iItem

                                                                                                  Editor_Select(#EdEmailInvalid, 0, gCharPos()\iStartChar, 0, gCharPos()\iEndChar)
                                                                                                  Break
                                                                                        EndIf
                                                                                Next
                                                                        EndIf

                                          EndSelect
                     EndSelect

              Until iExit = #True
EndProcedure

;=== Example ========================================
  WinEmailInvalid()
CheckEmailAddress("donald..duck2016@-gmail...co[uk-")
 EmailInvalidWait()
[/size]

Edit:
Added 'iLastAtChar' because the Domain Part of the address would not be extracted if there were two @@ in a row.
Added 'ClearList(gCharPos())' after 'ClearGadgetItems(#LstEmailInvalid)' since CheckEmailAddress() could be re-visited in the same session.
Last edited by IdeasVacuum on Mon Sep 05, 2016 12:45 am, edited 3 times in total.
IdeasVacuum
If it sounds simple, you have not grasped the complexity.
IdeasVacuum
Always Here
Always Here
Posts: 6426
Joined: Fri Oct 23, 2009 2:33 am
Location: Wales, UK
Contact:

Re: Email Address Validation

Post by IdeasVacuum »

..and the included file for RTF:

Code: Select all

;Based on some Powerbasic code found at http://www.hellobasic.com by Edwin Knoppert
;and translated to Purebasic by Stephen Rodriguez (sRod).

;Some useful functions for formatting text and paragraphs within EditorGadgets by Freak.
;Additions by Hroudtwolf, sRod, IdeasVacuum.

#CFM_BACKCOLOR = $4000000
#CFM_ALLCAPS = $80
#CFM_ANIMATION = $40000
#CFM_BACKCOLOR = $4000000
#CFM_BOLD = $1
#CFM_CHARSET = $8000000
;#CFM_COLOR = $40000000
#CFM_DISABLED = $2000
#CFM_EMBOSS = $800
#CFM_FACE = $20000000
#CFM_HIDDEN = $100
#CFM_IMPRINT = $1000
#CFM_ITALIC = $2
#CFM_KERNING = $100000
#CFM_LCID = $2000000
#CFM_LINK = $20
#CFM_OFFSET = $10000000
#CFM_OUTLINE = $200
#CFM_PROTECTED = $10
#CFM_REVAUTHOR = $8000
#CFM_REVISED = $4000
#CFM_SHADOW = $400
#CFM_SIZE = $80000000
#CFM_SMALLCAPS = $40
#CFM_SPACING = $200000
#CFM_STRIKEOUT = $8
#CFM_STYLE = $80000
;#CFM_SUPERSCRIPT = $30000
#CFM_UNDERLINE = $4
#CFM_UNDERLINETYPE = $800000
#CFM_WEIGHT = $400000
#CFE_ALLCAPS = #CFM_ALLCAPS
#CFE_AUTOBACKCOLOR = #CFM_BACKCOLOR
#CFE_AUTOCOLOR = $40000000
#CFE_BOLD = #CFM_BOLD
#CFE_DISABLED = #CFM_DISABLED
#CFE_EMBOSS = #CFM_EMBOSS
#CFE_HIDDEN = #CFM_HIDDEN
#CFE_IMPRINT = #CFM_IMPRINT
#CFE_ITALIC = #CFM_ITALIC
#CFE_LINK = #CFM_LINK
#CFE_OUTLINE = #CFM_OUTLINE
#CFE_PROTECTED = #CFM_PROTECTED
#CFE_REVISED = #CFM_REVISED
#CFE_SHADOW = #CFM_SHADOW
#CFE_SMALLCAPS = #CFM_SMALLCAPS
#CFE_STRIKEOUT = $8
#CFE_SUBSCRIPT = $10000
#CFE_SUPERSCRIPT = $20000
#CFE_UNDERLINE = $4
#CFM_SUBSCRIPT = #CFE_SUBSCRIPT | #CFE_SUPERSCRIPT

#ENM_LINK = $04000000


#PFA_LEFT = 1
#PFA_RIGHT = 2
#PFA_CENTER = 3
#PFA_JUSTIFY = 4
#PFM_ALIGNMENT = 8

#ST_DEFAULT = 0
#ST_KEEPUNDO = 1
#ST_SELECTION = 2

;=== Declares ===
Declare.i Editor_EmptyUndoBuffer(iGgtID.i)
Declare.i Editor_CanUndo(iGgtID.i)
Declare Editor_Undo(iGgtID.i)
Declare Editor_Redo(iGgtID.i)
Declare Editor_CursorToEot(iGgtID.i)
Declare.s Editor_GetSelected(iGgtID.i)
Declare Editor_BackColour(iGgtID.i, iColour.i)
Declare Editor_FontColour(iGgtID.i, iColour.i)
Declare Editor_Font(iGgtID.i, sFontName.s)
Declare Editor_FontSize(iGgtID.i, iFontSizePts.i)
Declare Editor_Format(iGgtID.i, iFlags.i, iAlternate.i = 0)
Declare Editor_Select(iGgtID.i, iLineStart.i, iCharStart.i, iLineEnd.i, iCharEnd.i)
Declare Editor_Bulleted(iGgtID.i)
Declare Editor_JustifyPara(iGgtID.i, iJustify.i)
Declare Editor_CopyText(iGgtID.i)
Declare Editor_CutText(iGgtID.i)
Declare Editor_InsertText(iGgtID.i, sTxt.s)
Declare Editor_PasteText(iGgtID.i)
Declare.i StreamFileInCallback(dwCookie, iPbBuff.i, lCb.l, lPcb.l)
Declare.i Editor_LoadRTF(iGgtID.i, sFilename.s, iReplaceAll.i = 0)
Declare.i StreamFileOutCallback(dwCookie, iPbBuff.i, lCb.l, lPcb.l)
Declare.i Editor_SaveRTF(iGgtID.i, sFilename.s)
Declare.s Editor_GetFont(iGgtID.i)
Declare.i Editor_GetFontSize(iGgtID.i)
Declare.i Editor_GetFontColor(iGgtID.i)
Declare.i Editor_GetAlignment(iGgtID.i)
Declare Editor_GetFontStyle(iGgtID.i, *iBold, *iItalic, *iStrike, *iUline)


Procedure.i Editor_EmptyUndoBuffer(iGgtID.i)
;#------------------------------------------
              ProcedureReturn SendMessage_(GadgetID(iGgtID), #EM_EMPTYUNDOBUFFER, 0, 0)
EndProcedure

Procedure.i Editor_CanUndo(iGgtID.i)
;#----------------------------------

              ProcedureReturn SendMessage_(GadgetID(iGgtID), #EM_CANUNDO, 0, 0)
EndProcedure

Procedure Editor_Undo(iGgtID.i)
;#-----------------------------

              SendMessage_(GadgetID(iGgtID), #EM_UNDO, 0, 0)
EndProcedure

Procedure Editor_Redo(iGgtID.i)
;#-----------------------------

              SendMessage_(GadgetID(iGgtID), #EM_REDO, 0, 0)
EndProcedure

Procedure Editor_CursorToEot(iGgtID.i)
;#--------------------------------------
;Set the cursor to the end of the text
Protected iLastLine.i = (CountGadgetItems(iGgtID) - 1)
Protected iLastChar.i = Len(GetGadgetText(iGgtID))

              Editor_Select(iGgtID, 0, iLastChar, iLastLine, iLastChar)

EndProcedure

Procedure.s Editor_GetSelected(iGgtID.i)
;#--------------------------------------
;Find User-selected text, if any
Protected sSelected.s, Range.CHARRANGE, iSize.i

                  SendMessage_(GadgetID(iGgtID), #EM_EXGETSEL, 0, Range)

                      iSize = (Range\cpMax - Range\cpMin)
                  sSelected = Space((iSize * 2) + 1)

                  SendMessage_(GadgetID(iGgtID), #EM_GETSELTEXT, 0, sSelected)

               ProcedureReturn(sSelected)
EndProcedure

;=== Character formatting ===
Procedure Editor_BackColour(iGgtID.i, iColour.i)
;#----------------------------------------------
Protected Format.CHARFORMAT2
          Format\cbSize = SizeOf(CHARFORMAT2)
          Format\dwMask = #CFM_BACKCOLOR
          Format\crBackColor = iColour

              SendMessage_(GadgetID(iGgtID), #EM_SETCHARFORMAT, #SCF_SELECTION, @Format)
EndProcedure

Procedure Editor_FontColour(iGgtID.i, iColour.i)
;#----------------------------------------------
;Set the Text Colour for the Selection (RGB)
Protected Format.CHARFORMAT2
          Format\cbSize = SizeOf(CHARFORMAT2)
          Format\dwMask = #CFM_COLOR
          Format\crTextColor = iColour

               SendMessage_(GadgetID(iGgtID), #EM_SETCHARFORMAT, #SCF_SELECTION, @Format)
EndProcedure

Procedure Editor_Font(iGgtID.i, sFontName.s)
;#------------------------------------------
;Set Font for the Selection
;You must specify a font name, the font doesn't need to be loaded

Protected Format.CHARFORMAT2
          Format\cbSize = SizeOf(CHARFORMAT2)
          Format\dwMask = #CFM_FACE

                     PokeS(@Format\szFaceName, sFontName)
              SendMessage_(GadgetID(iGgtID), #EM_SETCHARFORMAT, #SCF_SELECTION, @Format)
EndProcedure

Procedure Editor_FontSize(iGgtID.i, iFontSizePts.i)
;#-------------------------------------------------
;Set Font Size for the Selection: in Pts
;yHeight = Char height measured in twips. A twip is 1/1440 inches, 1/20 of a Pt

Protected Format.CHARFORMAT2
          Format\cbSize  = SizeOf(CHARFORMAT2)
          Format\dwMask  = #CFM_SIZE
          Format\yHeight = iFontSizePts * 20 ;Converts Pts to twips

              SendMessage_(GadgetID(iGgtID), #EM_SETCHARFORMAT, #SCF_SELECTION, @Format)
EndProcedure

Procedure Editor_Format(iGgtID.i, iFlags.i, iAlternate.i = 0)
;#-----------------------------------------------------------
; Set Format of the Selection. This can be a combination of
; the following values:
; #CFE_BOLD
; #CFE_ITALIC
; #CFE_UNDERLINE
; #CFE_STRIKEOUT
; #CFE_LINK
; #CFE_SUBSCRIPT
; #CFE_SUPERSCRIPT
;If the optional parameter 'iAlternate' is non-zero then the formatting attributes specified in
;'iFlags' will be xored with those already present within the first character of the selection.
;This has the effect of removing individual attributes if already present.
;e.g. specifying #CFE_BOLD on an already bold selection, will remove the bold formatting etc.

Protected Format.CHARFORMAT2
          Format\cbSize = SizeOf(CHARFORMAT2)

              If iAlternate

                     SendMessage_(GadgetID(iGgtID), #EM_GETCHARFORMAT, 1, @Format)

                     iFlags = Format\dwEffects!iFlags
              EndIf

              Format\dwMask    = #CFM_ITALIC|#CFM_BOLD|#CFM_STRIKEOUT|#CFM_UNDERLINE|#CFM_LINK|#CFM_SUBSCRIPT|#CFM_SUPERSCRIPT
              Format\dwEffects = iFlags

              SendMessage_(GadgetID(iGgtID), #EM_SETCHARFORMAT, #SCF_SELECTION, @Format)
EndProcedure

Procedure Editor_Select(iGgtID.i, iLineStart.i, iCharStart.i, iLineEnd.i, iCharEnd.i)
;#-----------------------------------------------------------------------------------
; Selects Text inside an EditorGadget
; Line numbers range from 0 to CountGadgetItems(#Gadget)-1
; Char numbers range from 1 to the length of a line
; Set Line numbers to -1 to indicate the last line, and Char
; numbers to -1 to indicate the end of a line
; selecting from 0, 1 to -1, -1 selects all.

Protected Sel.CHARRANGE
          Sel\cpMin = SendMessage_(GadgetID(iGgtID), #EM_LINEINDEX, iLineStart, 0) + iCharStart - 1

              If iLineEnd = -1

                     iLineEnd = SendMessage_(GadgetID(iGgtID), #EM_GETLINECOUNT, 0, 0)-1
              EndIf

              Sel\cpMax = SendMessage_(GadgetID(iGgtID), #EM_LINEINDEX, iLineEnd, 0)

              If iCharEnd = -1

                     Sel\cpMax + SendMessage_(GadgetID(iGgtID), #EM_LINELENGTH, Sel\cpMax, 0)
              Else
                     Sel\cpMax + iCharEnd - 1
              EndIf

              SendMessage_(GadgetID(iGgtID), #EM_EXSETSEL, 0, @Sel)
EndProcedure

;=== Paragraph formatting ===
Procedure Editor_Bulleted(iGgtID.i)
;#---------------------------------
Protected Format.PARAFORMAT
          Format\cbSize = SizeOf(PARAFORMAT)
          Format\dwMask = #PFM_NUMBERING
          Format\wnumbering = #PFN_BULLET

              SendMessage_(GadgetID(iGgtID), #EM_SETPARAFORMAT, 0, @Format)
EndProcedure

Procedure Editor_JustifyPara(iGgtID.i, iJustify.i)
;#------------------------------------------------
;Set paragraph justification to one of the following:
;#PFA_LEFT
;#PFA_RIGHT
;#PFA_CENTER

Protected Format.PARAFORMAT
          Format\cbSize = SizeOf(PARAFORMAT)
          Format\dwMask = #PFM_ALIGNMENT
          Format\wAlignment = iJustify

               SendMessage_(GadgetID(iGgtID), #EM_SETPARAFORMAT, 0, @Format)
EndProcedure

;=== Clipboard Functions ===
Procedure Editor_CopyText(iGgtID.i)
;#---------------------------------
              SendMessage_(GadgetID(iGgtID), #WM_COPY, 0, 0)
EndProcedure

Procedure  Editor_CutText(iGgtID.i)
;#---------------------------------
              SendMessage_(GadgetID(iGgtID), #WM_CUT, 0, 0)
EndProcedure

Procedure Editor_InsertText(iGgtID.i, sTxt.s)
;#-------------------------------------------
              SendMessage_(GadgetID(iGgtID), #EM_REPLACESEL, 0, sTxt)
EndProcedure

Procedure Editor_PasteText(iGgtID.i)
;#----------------------------------
              SendMessage_(GadgetID(iGgtID), #WM_PASTE, 0, 0)
EndProcedure

;=== Streaming ===
Procedure.i StreamFileInCallback(dwCookie, iPbBuff.i, lCb.l, lPcb.l)
;#------------------------------------------------------------------
;Called repeatedly by Windows to stream data into an editor gadget from an external file.

Protected iResult.i = 0
Protected lLength.l = ReadData(dwCookie, iPbBuff, lCb)

              PokeL(lPcb, lLength)

              If(lLength = 0)

                     iResult = 1
              EndIf

              ProcedureReturn iResult
EndProcedure

Procedure.i Editor_LoadRTF(iGgtID.i, sFilename.s, iReplaceAll.i = 0)
;#------------------------------------------------------------------
;Load an rtf file into an editor gadget. Return zero if no error encountered.
;The optional parameter 'iReplaceAll' can be set to #SFF_SELECTION to replace the current selection only.

Protected edstr.EDITSTREAM
          edstr\dwCookie = ReadFile(#PB_Any, sFilename)

              If edstr\dwCookie

                     edstr\dwError = 0
                     edstr\pfnCallback = @StreamFileInCallback()
                     SendMessage_(GadgetID(iGgtID), #EM_STREAMIN, #SF_RTF|iReplaceAll, edstr)
                        CloseFile(edstr\dwCookie)

                     ProcedureReturn(edstr\dwError)
              Else
                     ProcedureReturn(1)
              EndIf
EndProcedure

Procedure.i StreamFileOutCallback(dwCookie, iPbBuff.i, lCb.l, lPcb.l)
;#-------------------------------------------------------------------
;Called repeatedly by Windows to stream data from an editor gadget to an external file.

Protected iResult.i = 0

              WriteData(dwCookie, iPbBuff, lCb)

              PokeL(lPcb, lCb)

              If lCb = 0

                     iResult = 1
              EndIf

              ProcedureReturn iResult
EndProcedure

Procedure.i Editor_SaveRTF(iGgtID.i, sFilename.s)
;#-----------------------------------------------
;Save the rtf content of an editor gadget to an external file. Returns zero if no error encountered.

Protected edstr.EDITSTREAM
          edstr\dwCookie = CreateFile(#PB_Any, sFilename)

              If edstr\dwCookie

                      edstr\dwError = 0
                      edstr\pfnCallback = @StreamFileOutCallback()
                      SendMessage_(GadgetID(iGgtID), #EM_STREAMOUT, #SF_RTF, edstr)
                         CloseFile(edstr\dwCookie)

                      ProcedureReturn edstr\dwError
              Else
                      ProcedureReturn 1
              EndIf
EndProcedure

;=== FUNCTIONS TO REVERSE-ENGINEER EDITOR'S RTF FORMAT (To create a PDF for example)
Procedure.s Editor_GetFont(iGgtID.i)
;#----------------------------------
Protected Format.CHARFORMAT2
Protected sFont.s

              Format\cbSize = SizeOf(CHARFORMAT2)
              SendMessage_(GadgetID(iGgtID), #EM_GETCHARFORMAT, #SCF_SELECTION, @Format)

                 sFont = PeekS(@Format\szFaceName[0])
              If sFont = ""

                      SendMessage_(GadgetID(iGgtID), #EM_GETCHARFORMAT, #SCF_DEFAULT, @Format)
                     sFont = PeekS(@Format\szFaceName[0])
              EndIf

              ProcedureReturn sFont
EndProcedure

Procedure.i Editor_GetFontSize(iGgtID.i)
;#--------------------------------------
Protected Format.CHARFORMAT2
Protected iFontSizePts.i

              Format\cbSize = SizeOf(CHARFORMAT2)
              SendMessage_(GadgetID(iGgtID), #EM_GETCHARFORMAT, #SCF_SELECTION, @Format)
               iFontSizePts = Format\yHeight / 20

              ProcedureReturn iFontSizePts
EndProcedure

Procedure.i Editor_GetFontColor(iGgtID.i)
;#---------------------------------------
Protected iRetColour.i
Protected Format.CHARFORMAT2

              Format\cbSize = SizeOf(CHARFORMAT2)
              SendMessage_(GadgetID(iGgtID), #EM_GETCHARFORMAT, #SCF_SELECTION, @Format)

              If (Format\dwEffects & #CFE_AUTOCOLOR) = #CFE_AUTOCOLOR

                       iRetColour = GetSysColor_(#COLOR_WINDOWTEXT)
              Else
                       iRetColour = Format\crTextColor
              EndIf

              ProcedureReturn iRetColour
EndProcedure

Procedure.i Editor_GetAlignment(iGgtID.i)
;#---------------------------------------
Protected  Format.PARAFORMAT\cbSize = SizeOf(PARAFORMAT)
           Format\dwMask = #PFM_ALIGNMENT

              SendMessage_(GadgetID(iGgtID), #EM_GETPARAFORMAT, #Null, @Format) ;Returns alignment of pre-selected line

              ProcedureReturn Format\wAlignment
EndProcedure

Procedure Editor_GetFontStyle(iGgtID.i, *iBold, *iItalic, *iStrike, *iUline)
;#--------------------------------------------------------------------------
Protected Format.CHARFORMAT2

              Format\cbSize = SizeOf(CHARFORMAT2)
              Format\dwMask = #CFM_BOLD | #CFM_ITALIC | #CFM_STRIKEOUT | #CFM_UNDERLINE

              SendMessage_(GadgetID(iGgtID), #EM_GETCHARFORMAT, #SCF_SELECTION, @Format)

              If Format\dwEffects & #CFM_BOLD

                     PokeI(*iBold, #True)
              EndIf
              If Format\dwEffects & #CFM_ITALIC

                     PokeI(*iItalic, #True)
              EndIf
              If Format\dwEffects & #CFM_STRIKEOUT

                     PokeI(*iStrike, #True)
              EndIf
              If Format\dwEffects & #CFM_UNDERLINE

                     PokeI(*iUline, #True)
              EndIf
EndProcedure
[/size]
IdeasVacuum
If it sounds simple, you have not grasped the complexity.
Post Reply