29 January 2012

Gfa_Setting() returns extra #0

Gfa_Setting is available as a statement and as a function. They are used as follows:

' Set/Create a subkey
Gfa_Setting("MySubKey") = mysetting$
' Get subkey value ($)
mysetting$ = Gfa_Setting("MySubKey")
' Delete a subkey
Gfa_Setting("MySubKey") = ""

The Gfa_Setting(subkey$) reads a value from the subkey under HKEY_CURRENT_USER\Software\GFA\Basic using the RegQueryValueEx() API function that retrieves the data for a specified value name associated with an open registry key. The advantage of using this API is that it doesn't require a data type. Even better, the function returns a code indicating the type of data stored in the specified value. The possible type codes are:

Const REG_NONE  = 0
Const REG_SZ  = 1
Const REG_EXPAND_SZ  = 2
Const REG_BINARY  = 3
Const REG_DWORD  = 4
Const REG_LINK  = 6
Const REG_MULTI_SZ  = 7

After invoking RegQueryValueEx GB32 checks the value in the type parameter and than decides how to copy the data to the GB32 variable used in the var$ = Gfa_Setting(subkey$) code. When the type value returned is REG_SZ or REG_EXPAND_SZ or REG_BINARY then GB32 uses the StrPeek(addr, size) function to create and copy the data to the string. The size of the data is also returned by RegQueryValueEx. Simple enough. But there is a catch that is overlooked:

"If the data has the REG_SZ, REG_MULTI_SZ or REG_EXPAND_SZ type, then the data will also include the size of the terminating null character or characters."

GB32 uses the size value to invoke StrPeek and stores the terminating #0 character inside the string and sets the length of the string to the size of data + 1 for the #0 character. GB32 uses the same function for Gfa_BinSetting() and does not care for the value of the size.

Now, when you apply a string concatenation (str & str2) the second string doesn't seem to be appended to the first string. The including #0 character in the last position of the string makes the use of string functions awkward. When you pass that string to a function that expects a C-string the string data is read to the first #0 character. Internally, and externally on the API level, many functions expect a C-string and stop processing the string at first occurrence of a #0 character.  

For now, you best use ZTrim() on the return value of Gfa_Setting(). It seems not to predict how many #0 characters will be included. See underlined words in the quotation.

' Get subkey string value
mysetting$ = ZTrim(Gfa_Setting("MySubKey"))

20 January 2012

Gfa_Line doesn't always redraw the caret

The GFA-BASIC implementation of the editor-extension contains a large set of Gfa_* functions and commands. However, in contrast with the term 'editor extension', the Gfa_* statements are not restricted to the (text) editor only. The GFA-BASIC IDE application itself is divided into several entities such as an application part, a form-editor, a compiler, and a text-editor. Only a part of the Gfa_ statements apply to the text-editor only, and the most important terms in this respect are 'text-selection' and 'insertion point'.

The active point
The text-selection represents a selection in the code text in the editor window. Note that a selection can be a single point (the insertion point) or a contiguous range of text. Using Gfa_ commands you can move the insertion point, control which text is selected, and modify the contents of the selection. When the text-selection is restricted to a single point, Gfa_Line and Gfa_Col return its position. Also, since there is no selection, Gfa_Line equals Gfa_SelLine and Gfa_Col equals Gfa_SelCol.

Whether or not the text is selected, the Gfa_Line and Gfa_Col always indicate the active point. This is the point where all other text actions are performed. When a range of text is selected the insertion point can be located at the beginning or at the end of the selection.

Command Description
Gfa_Line Gets/sets the line where the insertion point is.
Gfa_Col Gets/sets the column where the insertion point is.
Gfa_SelLine Gets/sets the line at the bottom of a selection. The bottom line is not necessarily at the active end of the selection.
Gfa_SelCol Gets/sets the column at the bottom of a selection. The bottom line is not necessarily at the active end of the selection.

Moving the caret
The insertion point is represented by a caret, is a blinking line, block, or bitmap in the client area of a window. The caret typically indicates the place at which text (or graphics) will be inserted. The caret is a system resource and controlled by invoking Windows system API calls such as ShowCaret() and SetCaretPosition(). Each time the caret is moved SetCaretPosition() is invoked.
Programmatically, you move the caret using one of the following Gfa_ commands:

Command Key Press
Gfa_Left [count ] LEFT ARROW
Gfa_Right [count] RIGHT ARROW
Gfa_Down [count] DOWN ARROW
Gfa_Up [count] UP ARROW
Gfa_PageDown PAGE DOWN
Gfa_PageUp PAGE UP

count is optional. A Long that specifies the number of times you want to repeat this action. The default is one.

  • When executing one of these commands (or pressing the key(s)) the current selection is canceled before the action is performed.
  • These are relative movements. They move the caret relative to the current active insertion point represented by Gfa_Col and Gfa_Line.
  • Many key press actions are not represented by a Gfa_ command. For instance: word-left, word-right; end-of-line, start-of-line; etc.

So, how do you move the caret to an absolute position, rather than to a relative one? Within a text line the Gfa_Col = newcolumn command will do the trick. It cancels any selection and updates the caret position. By assigning 0 to Gfa_Col the caret moves to the start-of-the-line and by setting it to some large number (_maxInt) is moves to the end-of-the-line. Consequently, you expect Gfa_Line to do the same for lines. Well it does modify the current line, but it sometimes 'forget" to update the caret visually. Meaning, it forgets to put the caret into its new position.

Gfa_Line = doesn't redraw caret
The Gfa_Line = command differs in two aspects from all other commands. First it doesn't cancel a selection, and, secondly, it doesn't always redraw the caret in it's new position. It does however change the current line used for text operations, which is important because some text operations are line oriented. So, without displaying the caret at the new position (combined with a possible scrolling) the current active line can be changed to perform actions on the text line (Gfa_Update, Gfa_Text = , delete and insert lines, setting a bookmark, clipboard actions, etc). The problem is that the editor extension implementation lacks a command to update the caret explicitly.

Workaround to redraw caret
To explicitly make the caret visible after invoking Gfa_Line = we must take into account that as portion of the text maybe selected. So, first we must cancel any selection and after invoking Gfa_Line =  we must redraw the caret ourselves.

Global Gfa_SetCaret As Pointer Byte
Pointer(Gfa_SetCaret)       = $4DC41C
Global Const Ed_SetCaretPos = $409B99

' Cancel selection (if any)
Gfa_Col = 0 ' Set/Goto new line Gfa_Line = iGotoLine ' Set global flag (when in OnWMCommand) Gfa_SetCaret = 1 ' Or call directly ~StdCall(Ed_SetCaretPos)()

Most (if not all) IDE commands are processed in the IDE's OnWMCommand() function. This is a central routine to execute all accelerator key commands, menu commands, and the GLL's key and menu events. Besides this, it adds the commands to the UNDO-buffer and stores them in a keyboard macro when Gfa_IsRecording = True. At the end of IDE's OnWMCommand() function the global variable Gfa_IsCaret is checked to see if any of the executed commands did set this flag (to have the caret redrawn). If so, before returning from OnWMCommand() the IDE's Ed_SetCaretPos() function is invoked to redraw the caret and Gfa_SetCaret is cleared.

Now you have some background knowledge of Gfa_Line you have the tools to properly redraw the caret.