VFP's Editor Code RTF2HTML (Part 4)

Version: 2.00.02 - last Update: Thursday, April 27, 2008, 23:53:00

Previous ChapterToolbox Home (TOC)Next Chapter


This thread is all about how to get VFP’s syntax coloured code to an HTML-formatted Blog like this is.

Intro

Today we will talk about FoxPro's syntax colouring in detail. You will learn what kind of information VFP is storing and where all the colour data is coming from. You will learn which VFP syntax areas are mapping to which registry values and what RGB() colour pairs are mapping to what kind of RTF control words. Finally I will give you some code to read VFP's colour entries out of the Windows registry. Let's go...

FoxPro's Syntax Colouring

At the end of part 2 we talked about VFP's syntax colour settings. Figure #1 below shows my test configuration:

 SyntaxColorText.Prg pasted into clipboard
Figure #1: SyntaxColorText.Prg pasted into clipboard

VFP stores all colour settings under HKEY_CURRENT_USER registry's hive. Figure #2 shows my registry content under
Software\Microsoft\VisualFoxPro\9.0\Options

Corresponding entries in the registry 
Figure #2: Corresponding editor entries in the registry

Resetting Syntax Colours to Defaults

If you click the default button (shown in Figure #2b below) you will reset all syntax colours to the internal defaults.

Built-in syntax colour defaults 
Figure #2b: Built-in syntax colour defaults

As we can see, there are always two entries reading Auto or NoAuto after the RGB() fore- and background colour pairs. As long as we select a colour other than  Automatic the registry entry will be "NoAuto". All Automatic colour selections we've made within VFP's options dialogue will be reflected by an "Auto" registry entry accordingly. Figure 2b above shows us all built-in VFP default values (after a complete reset). The next table #1 below lists all "Automatic" values for all of VFP's syntax fore- and background colours. The listed RGB() values below are applied if we choose to reset VFP's colour values.

Area ID Foreground Background
EditorCommentColor RGB(0,128,0) RGB(255,255,255)
EditorKeywordColor RGB(0,0,255) RGB(255,255,255)
EditorConstantColor RGB(0,0,0) RGB(255,255,255)
EditorNormalColor RGB(0,0,0) RGB(255,255,255)
EditorOperatorColor RGB(0,0,0) RGB(255,255,255)
EditorStringColor RGB(0,0,0) RGB(255,255,255)
EditorVariableColor RGB(0,0,0) RGB(255,255,255)

Table #1: Default Syntax Colours after a RESET ALL

General Font Style Registry Mappings

The font style Automatic gets represented by the value -1 in the registry. Table #2 below shows all values possible:

Font Style Registry value Meaning
Automatic -1 Use VFP'S internal "reset-all default"
Normal 0 apply neither bold nor italic formatting
Bold 1 apply bold formatting
Italic 2 apply italic formatting
Bold Italic 3 apply bold and italic formatting
Table #2: Registry's font style index values

All about EditorNormalColor and Style

Firstly: The setting "EditorNormalColor" is used for background colouring only if you have syntax colouring turned ON, like shown in figure #3 below:

Background with syntax colouring ON

Figure #3: Background with syntax colouring ON

SecondlyThe other settings of "Area Normal" are only used when you turn syntax colouring OFF - either for a single source code editor session via it's context menu, or generally via VFP's options->IDE settings. Figure #4 below shows the relation:

Disabling syntax colouring

Figure #4: Disabling syntax colouring activates "Area-Normal" settings

Remember: you have to use VFP's option dialogue button Set As Default to flush your work back to the registry before our little tool can retrieve and apply these changed settings to the final HTML output!

BTW: Disabling syntax colouring stops VFP stuffing any RTF content into the Windows clipboard!! (After I had finished some - as I thought - cool RTF-Clipboard retrieval code block of code - GOSH, there it was: my next inspiration of the day!) As you might have guessed - we have to talk about this issue in detail later!

Additional Note about EditorNormalColor

It is good to know, that you are not limited to the internal colour palette VFP offers you when choosing a fore-or background colour in the Options dialogue. Go to the registry Software\Microsoft\VisualFoxPro\9.0\Options-hive instead and replace, let's say, the back-colour of the Normal area for instance like shown in figure #5 below:

An individual editor back-colour 
Figure #5: An individual editor back-colour

The Automatic Syntax Colour Option

Finally it is good to know where the "automatic colours" are coming from. As we know by now VFP ignores all RGB() syntax colour values stored in the registry as long we've chosen Automatic in the Options dialogue. There are two different scenarios:
1st scenario: We have chosen an Automatic colour for VFP's Area Normal. I
n this case VFP retrieves the appropriate colour setting from Windows directly! Figure #5 below shows my German display settings where I changed the window background colour of the active window to green (GLOBALLY!). My running VFP instance immediately reflected the change, because my Area Normal background colouring was set to Automatic in that demo.

Automatic syntax colour for Area Normal
Figure #6: Automatic syntax colour for Area Normal

2nd scenario: We have chosen an Automatic colour for any other VFP Area but Normal. In these cases VFP reads the colour setting from it's own Area Normal settings like shown in Figure #7 below:

Automatic syntax colour for all other Areas 

Figure #7: Automatic syntax colour for all other Areas

Wow, folks, that hurts ;-) I hope I could shed some light on that "colourful" chapter of VFP's Better-to-Forget internals...

RTF Colour Mappings

Let's have a look at our syntax colour test code and compare it's text colours with the the RTF colour table now:

Our SyntaxColorText.Prg ...
* Lucy.prg
USE fred TAG ethel
m.nAmount = 1213.45 * 10
m.cName = "Ricky"
... generates the following RTF colour table
{\rtf1{\colortbl
\red0\green128\blue0;
\red0\green0\blue255;
\red255\green0\blue0;
\red0\green0\blue0;
\red255\green0\blue255;
\red255\green255\blue0;
\red128\green0\blue128;}}

Now we can relate the colour table entries of VFP's clipboard data to VFP's editor keyword mapping in the registry like listed in table #3 below:

Registry entry RTF colour index RTF control word
EditorCommentColor 0 \cf0
EditorKeywordColor 1 \cf1
EditorConstantColor 2 \cf2
EditorNormalColor 3 \cf3
EditorOperatorColor 4 \cf4
EditorStringColor 5 \cf5
EditorVariableColor 6 \cf6
Table #3: Registry to colour index mapping

This is pretty cool, because we are able to retrieve the missing background colour information out of the Windows registry directly!

Background Colour Workaround

Since we know by now what RTF colour table index (control word) matches what VFP editor's registry entry, we can fetch the missing background colour information out of the Windows registry directly. Even better! We do not need to parse the entire colour table in the RTF header! The following steps have to be taken:

  • Open Windows Registry
  • Read VFP editor's syntax colour information
  • Create a mapping array holding fore- and background colours
  • Map colour-pairs to the corresponding RTF control words

Well, that sounds easy to do - almost! You already know that there are some more steps to be taken to achieve a final one-to-one looking result! Thus, the final implementation has to care about the Auto/NoAuto registry settings for each syntax colour area. It has to map those areas marked as an Automatic colour setting to the Area Normal. Even better, if VFP's Area Normal has an Automatic setting on it's own, we have to look up the corresponding Windows values first! Again, this can be done automatically reading the right registry values at program launch. Or we can implement some leaner solution by just using discrete setup variables that can be set through a configuration dialogue by the user.

Accessing the Registry

Of course we could utilize VFP's own registry class like shown in listing #1 below without problems:

Using VFP's FoxReg-Object
regfile = HOME(2)+"classes\registry.prg"
SET PROCEDURE TO (m.regfile) ADDITIVE
oReg = CreateObject("FoxReg")
Listing #1: accessing the Windows registry

We only need read access by now. Using VFP's FoxReg object would be a little bit to much of a good thing. Let's implement our own registry reader based on one of Anatoliy Mogylevets's free API example you can find here.

Our own Registry Reader

*\\ Function GetRegKey
LPARAMETERS tcValue AS STRING, tcBaseKey AS STRING
*//
#DEFINE HKEY_CURRENT_USER 0x80000001
#DEFINE ERROR_SUCCESS    
0
#DEFINE KEY_READ          131097
*//
*\\ Mandatory parameter checking
IF NOT VARTYPE(m.tcValue) == "C" ;
        OR EMPTY(m.tcValue)
   
*\\ NULL := Error condition!
   
RETURN NULL
   
*//
ENDIF
*//
*\\ Optional parameter normalization
IF NOT VARTYPE(m.tcBaseKey) == "C" ;
        OR EMPTY(m.tcBaseKey)
       tcBaseKey = ;
   "Software\Microsoft\VisualFoxPro\9.0\Options"
ELSE
    tcBaseKey = ALLTRIM(m.tcBaseKey)
ENDIF
*//
LOCAL hKey     AS INTEGER ,;
      lnSize   AS INTEGER ,;
      lcData   AS STRING  ,;
      lcResult AS
STRING
hKey = 0
lnSize = 250
lcData = SPACE(lnSize)
lcResult = NULL
*\\ Opening specified key
IF RegOpenKeyEx(               ;
        HKEY_CURRENT_USER    ,;
        m.tcBaseKey            ,;
        0                     ,;
        KEY_READ             ,;
        @hKey) = ERROR_SUCCESS 
    *\\ Querying the value name
    IF RegQueryValueEx(      ;
            m.hKey            ,;
            m.tcValue        ,;
            0                ,;
            0                ,;
            @lcData            ,;
            @lnSize) = ERROR_SUCCESS 
        *\\ return value
        lcResult = LEFT(m.lcData, m.lnSize-1) 
   
ENDIF
ENDIF
*\\ releasing key handle
= RegCloseKey(m.hKey)
RETURN m.lcResult

*\\ Don't forget to define these registry ;
   
related API calls
DECLARE INTEGER RegOpenKeyEx IN advapi32;
    INTEGER hKey,;
    STRING  lpSubKey,;
    INTEGER ulOptions,;
    INTEGER samDesired,;
    INTEGER @phkResult

DECLARE INTEGER RegQueryValueEx IN advapi32;
    INTEGER hKey,;
    STRING  lpValueName,;
    INTEGER lpReserved,;
    INTEGER @lpType,;
    STRING  @lpData,;
    INTEGER @lpcbData

DECLARE INTEGER RegCloseKey IN advapi32;
    INTEGER  hKey

Listing #2: Function GetRegKey()

Finally we need a method to fill an array like shown in listing #3 below:

Looping through VFP's Registry Entries

*\\ InitColorArray()
LOCAL lnLoop    AS Integer    ,;
      lcRegKey  AS String     ,;
      lcValue   AS String
WITH THIS
   *\\ Redimension array
   DIMENSION .aEditorColors[7,10] 
   *\\ Fill default values
   .aEditorColors[1,1] = "EditorCommentColor"
   .aEditorColors[2,1] = "EditorKeywordColor"
   .aEditorColors[3,1] = "EditorConstantColor"
   .aEditorColors[4,1] = "EditorNormalColor"
   .aEditorColors[5,1] = "EditorOperatorColor"
   .aEditorColors[6,1] = "EditorStringColor"
   .aEditorColors[7,1] = "EditorVariableColor" 
   *\\ Get color settings from registry
   lcRegKey = ;
   "Software\Microsoft\VisualFoxPro\9.0\Options"
   *// 
   FOR lnLoop = 1 TO ALEN(.aEditorColors,1)
       *\\ retrieve registry key value
       lcValue = .GetRegKey(;
                 .aEditorColors[m.lnLoop,1],;
                m.lcRegKey)
       IF NOT ISNULL(m.lcValue)
          *\\ no error condition
          .aEditorColors[m.lnLoop,2] = .lcValue
       ELSE
          *\\ in case of an error;
              default to black on white

          .aEditorColors[m.lnLoop,2] = ;
           "RGB(0,0,0,255,255,255), Auto, Auto"
       ENDIF
       *\\ parse RGB() color expression
       .ParseColorExpression(m.lnLoop)
   NEXT
   *//
ENDWITH

Listing #3: Procedure InitColorArray()

Finally, we parse the retrieved colour pairs and split them into array fields like shown in listing #4 below:

Parsing the retrieved colour values

LPARAMETERS tnColorIndex AS Integer
LOCAL   lcRGBexpr     AS String  ,;
        lnAtPos1      AS Integer ,;
        lnRGBArrayLen AS Integer ,;
        lnLoop        AS Integer

LOCAL ARRAY aRGB[1,6]
WITH THIS
    *\\ extract color values from string
    lcRGBexpr = .aEditorColors[m.tnColorIndex,2]
    lnATPos1  = AT("(",m.lcRGBexpr,1)+1
    lcRGBexpr = SUBSTR(m.lcRGBexpr   ,;
                        m.lnATPos1   ,;
                AT(")",m.lcRGBexpr,1)-;
                        m.lnATPos1)
    *// lcRGBexpr now looks like ;
        "0,128,0,255,255,255"

*\\ parse RGB values to temporary
array "aRGB"
    lnRGBArrayLen = ;
        ALINES(aRGB, m.lcRGBexpr,1,",")
*\\ copy RGB values to .aEditorColors array
    FOR lnLoop = 1 TO m.lnRGBArrayLen
      .aEditorColors[m.tnColorIndex, m.lnLoop + 1] = ;
        INT(VAL(aRGB[m.lnLoop]))
    NEXT
    *\\ calculate RGB() forecolor hex value
    .aEditorColors[m.tnColorIndex,8] = "#" + ;
       RIGHT(TRANSFORM(.aEditorColors[m.tnColorIndex,2],"@0"),2) + ;
       RIGHT(TRANSFORM(.aEditorColors[m.tnColorIndex,3],"@0"),2) + ;
       RIGHT(TRANSFORM(.aEditorColors[m.tnColorIndex,4],"@0"),2)
    *\\ calculate RGB() backcolor hex value
    .aEditorColors[m.tnColorIndex,9] =  "#" + ;
       RIGHT(TRANSFORM(.aEditorColors[m.tnColorIndex,5],"@0"),2) +;
       RIGHT(TRANSFORM(.aEditorColors[m.tnColorIndex,6],"@0"),2) +;
       RIGHT(TRANSFORM(.aEditorColors[m.tnColorIndex,7],"@0"),2)
    *\\ add RTF control word
    .aEditorColors[m.tnColorIndex,10] = ;
             "\cf"+TRANSFORM(m.tnColorIndex-1)
ENDWITH

Listing #4: Procedure ParseColorExpression()

Preview

That's all for now. In part 5 of this thread I will show you how to work around all quirks and oddities we've encountered so far.


Previous ChapterToolbox Home (TOC)Next Chapter

No comments:

Post a Comment