Monthly Archives: May 2021

String comparison in OpenInsight – Part 3 – Linguistic Mode

Welcome to the final part of this mini series on the string comparison mechanics in OpenInsight. In the first two parts we reviewed how this task is currently handled in both ANSI and UTF8 modes, but this time we’ll take a look at a new capability introduced for the next release which is called the “Linguistic String Comparison Mode”.

As we’ve seen previously, there is certainly room for improvement when dealing with string comparisons and sorting in non-English languages, mainly due to the burden placed on the developer to maintain the sorting parameters, especially once the requirements extend beyond the basic ANSI character set. There is also no advantage taken of the capabilities of Windows itself, which provides a comprehensive National Language Support (NLS) API for testing strings for linguistic equality.

What is “linguistic equality”?

If you’re unfamiliar with the term “linguistic equality” it essentially means comparing strings according to the language rules of a specific locale thereby providing appropriate results for a user of that locale. For example, consider the following cases that illustrate how comparisons differ for the same characters in different locales:

  • Many locales equate the ae ligature (æ) with the letters ae. However, Icelandic (Iceland) considers it a separate letter and places it after Z in the sorting sequence.
  • The A Ring (Å) normally sorts with merely a diacritic difference from A. However, Swedish (Sweden) places the A Ring after Z in the sorting sequence.

In a standard OI system these sort of rules would need the developer to define the collation sequence records that represent them, which is simply duplicating effort when Windows itself is easily capable of handling this for us.

Using Linguistic Mode

In order to utilize this API without impacting current systems we have introduced a new “mode” into OpenInsight that allows you to determine exactly when you wish to enable linguistic support. This mode comprises three elements:

  1. Mode ID – this is the mode itself, which can be one of the following values:
    • (0) Normal, non-linguistic mode.
    • (1) Linguistic mode.
  2. Mode Flags – A set of bit-wise flags for use with the Linguistic mode.
  3. Mode Locale – A locale identifier for use with the Linguistic mode (defaults to the current user’s locale).

It’s simply a case of setting the mode when you want it to apply to sorting and case-insensitive operations, and turning it off when you don’t. Just like with Extended Precision Mode you can set a default mode for your application and then adjust this at runtime as desired.

(Note that using the Linguistic mode is not affected by OpenInsight’s ANSI or UTF8 mode, as the string comparisons are processed “outside” in Windows itself.)

The following five functions are used to control the Linguistic Mode:

  • GetDefaultStrCmpMode – returns the default application mode settings.
  • SetDefaultStrCmpMode – sets the default application mode.
  • GetStrCmpMode – returns the current mode settings.
  • SetStrCmpMode – sets the current mode.
  • GetStrCmpStatus – returns the status of a string comparison operation.

Along with this set of equates:

  • RTI_STRCMPMODE_EQUATES
  • MSWIN_COMPARESTRING_EQUATES

Example:

$Insert RTI_StrCmpMode_Equates
$Insert MSWin_CompareString_Equates

// Set the mode to Linguistic, sorting digits as numbers, case-insensitive, 
// and with linguistic casing, using the "en-UK" locale
SCFlags = BitOr( LINGUISTIC_IGNORECASE$, NORM_LINGUISTIC_CASING$ )
SCFlags = BitOr( SCFlags, SORT_DIGITSASNUMBERS$ )

Call SetStrCmpMode( STRCMPMODE_LINGUISTIC$, SCFlags, "en-UK" ) 

// Now do some sorting ...
Call V119( "S", "", "A", "L", data, "" )

Full details on each of these functions can be found at the end of this post, but let’s take a look in more detail at the each of the mode settings:

Mode ID

This is an integer value that controls how string comparisons are made:

When set to “0” then the application will run in “normal” mode, which means that string comparisons will use the methods described in parts 1 and 2 of this series. The Mode Flags and Mode Locale settings are ignored.

When set to “1” the application uses the Windows CompareStringEx function for string comparisons instead. The Mode Flags and the Mode Locale settings will also be used with this.

Mode Flags

This setting is a integer comprising one or more optional bit-flags that are passed to the Windows CompareStringEx function when running in Linguistic Mode (It may be set to 0 to apply the default behavior). A full description of their use can be found in the Microsoft documentation for the CompareStringEx function, but briefly these are:

FlagDescription
LINGUISTIC_IGNORECASE$Ignore case, as linguistically appropriate.
LINGUISTIC_IGNOREDIACRITIC$Ignore nonspacing characters, as linguistically appropriate.
NORM_IGNORECASE$Ignore case.
NORM_IGNOREKANATYPE$Do not differentiate between hiragana and katakana characters.
NORM_IGNORENONSPACE$Ignore nonspacing characters.
NORM_IGNORESYMBOLS$Ignore symbols and punctuation.
NORM_IGNOREWIDTH$Ignore the difference between half-width and full-width characters.
NORM_LINGUISTIC_CASING$Use the default linguistic rules for casing, instead of file system rules.
SORT_DIGITSASNUMBERS$Treat digits as numbers during sorting.
SORT_STRINGSORT$Treat punctuation the same as symbols.

Mode Locale

This is can be the name of the locale to use (like “en-US”, “de-CH” etc.), or one of the following special values:

  • “0” or null – Use the current user locale (LOCALE_NAME_USER_DEFAULT).
  • “1” – Use the current OS locale (LOCALE_NAME_SYSTEM_DEFAULT).
  • “2” – Use an invariant locale that provides stable locale and calendar data (LOCALE_NAME_INVARIANT)

The Linguistic Mode and Basic+

The following Basic+ operators and functions are affected by the Linguistic Mode :

  • LT operator
  • LE operator
  • EQ operator
  • NE operator
  • GE operator
  • GT operator
  • _LTC operator
  • _LEC operator
  • _EQC operator
  • _NEC operator
  • _GEC operator
  • _GTC operator
  • IndexC function
  • V119 function
  • Locate By statement
  • LocateC statement

Note that when used with the case-insensitive operators and functions (such as _eqc, IndexC() etc.) the LINGUISTIC_IGNORECASE$ flag is always applied if the NORM_IGNORECASE$ has not been specified.

Performance considerations

Using the Linguistic Mode can impact performance for two reasons:

  1. There is just more work to do – comparison of strings using more complex rules will always be slower that a simple comparison of ordinal byte values or code points.
  2. The strings must be copied and transformed into UTF16 (wide) strings before passing to the Windows CompareStringEx function. While this is not a slow operation in and of itself it will add some overhead.

Because of this Linguistic Mode is not enabled by default – you are free choose when to apply it yourself.

String Comparison Mode functions

GetDefaultStrCmpMode function

This function returns an @fm-delimited dynamic array containing the current default string comparison mode settings for the application in the format:

<1> Mode
<2> Flags
<3> Locale

Example:

$Insert RTI_StrCmpMode_Equates

DefSCM  = GetDefaultStrCmpMode()
DefMode = DefSCM<GETSTRCMPMODE_MODE$>

SetDefaultStrCmpMode function

This function sets the default string comparison mode for an application. The mode is set to these default values for each new request made to the engine (i.e each event or web-request).  This is to protect against situations where an error condition could force the engine to abort processing before the mode could be reset, thereby leaving it in an unknown state.

This function takes three arguments:

NameDescription
ModeSpecifies the default mode to set: “0” for Normal mode, or “1” for Linguistic Mode.
FlagsBitmask integer that specifies the default flags to use when in Linguistic Mode
LocaleSpecifies the name of the default locale to use.

Example:

$Insert RTI_StrCmpMode_Equates
$Insert MSWin_CompareString_Equates

// Set the default mode to Linguistic, sorting digits as numbers, using the
// user's locale
SCFlags = SORT_DIGITSASNUMBERS$

Call SetDefaultStrCmpMode( STRCMPMODE_LINGUISTIC$, SCFlags, "" ) 

GetStrCmpMode function

This function returns an @fm-delimited dynamic array containing the current string comparison mode settings for the application in the format

<1> Mode
<2> Flags
<3> Locale

Example:

$Insert RTI_StrCmpMode_Equates

CurrSCMode = GetStrCmpMode()<GETSTRCMPMODE_MODE$>

SetStrCmpMode function

This function sets the current string comparison mode for an application. Note that the mode is set to the default values for each new request made to the engine (i.e each event or web-request).  This is to protect against situations where an error condition could force the engine to abort processing before the mode could be reset, thereby leaving it in an unknown state.

This function takes three arguments:

NameDescription
ModeSpecifies the mode to set: “0” for Normal mode, or “1” for Linguistic Mode.
FlagsBitmask integer that specifies the flags to use when in Linguistic Mode
LocaleSpecifies the name of the locale to use.

Example:

$Insert RTI_StrCmpMode_Equates
$Insert MSWin_CompareString_Equates

// Set the mode to Linguistic, sorting digits as numbers, case-insensitive, 
// and with linguistic casing, using the "en-UK" locale
SCFlags = BitOr( LINGUISTIC_IGNORECASE$, NORM_LINGUISTIC_CASING$ )
SCFlags = BitOr( SCFlags, SORT_DIGITSASNUMBERS$ )

Call SetStrCmpMode( STRCMPMODE_LINGUISTIC$, SCFlags, "en-UK" ) 

// Now do some sorting ...
Call V119( "S", "", "A", "L", data, "" )

GetStrCmpStatus function

While it is unlikely that the CompareStringEx function will raise any errors it is possible if incompatible flags or parameters are used. In this case Windows returns an error code which may be accessed in Basic+ via this function (See the CompareStringEx documentation for more details on error values).

Example:

$Insert RTI_StrCmpMode_Equates
$Insert MSWin_CompareString_Equates

// Set the mode to Linguistic, sorting digits as numbers, case-insensitive, 
// and with linguistic casing, using the "en-UK" locale
SCFlags = BitOr( LINGUISTIC_IGNORECASE$, NORM_LINGUISTIC_CASING$ )
SCFlags = BitOr( SCFlags, SORT_DIGITSASNUMBERS$ )

Call SetStrCmpMode( STRCMPMODE_LINGUISTIC$, SCFlags, "en-UK" ) 

// Now do some sorting ...
Call V119( "S", "", "A", "L", data, "" )

SCError = GetStrCmpStatus()
If SCError Then
   ErrorText = RTI_ErrorText( "WIN", SCError )
End

Conclusion

This concludes this mini-series on OpenInsight string comparison processing. Hopefully you’ll find the new Linguistic Mode useful in your own applications, bearing in mind that some of the custom sorting options, such as “Treat Digits As Numbers”, can have a use in any application beyond simply dealing with non-English language sets.

Some of the more astute readers among you may have noticed that no mention of indexing has been made so far with respect to Linguistic Mode. This is because work is currently ongoing in this part of the system, and we’ll give you more details regarding this at a later date.

Further reading

More information on this subject may be found here:

String comparison in OpenInsight – Part 2 – UTF8 Mode

Welcome to the second part of our mini-series explaining the mechanics of how string comparisons are handled in OpenInsight. In our previous post we looked at the inner workings when running in ANSI mode – this time we’ll look at UTF8 mode instead.

(Note that we’ve included some Basic+ pseudo-code examples in this post to illustrate more clearly how some parts of the comparison routines work. These are simplifications of the actual C++ internal functions and not actual code from the system itself.)

String comparison in UTF8 Mode

In UTF8 mode characters can be multi-byte and therefore have a value greater than 255 (normally referred to as their “code point”, or in Basic+ terms, the Seq() value of a character), so this means that the standard ANSI-mode method described previously cannot be used. Instead, a slightly different approach is taken to allow higher code points to be included in custom sorting.

When the system is loaded the UTF8 library creates an internal character-map (called the “ANSI-map”) which is a 256-element array (0-255) of code-point values. This is initialized to the same values as the standard ANSI character set, i.e. position 65 will have the code point for the ANSI character with the value of 65, position 230 will have the code point for the ANSI character with the value of 230 and so on.

This ANSI-map this can be changed at runtime so that code points that are higher than 255 can be included, and code points that appear in the ANSI-map are always sorted lower than those that aren’t, regardless of their actual value. The following functions (exported from RevUTF8.dll) are used to query and update the ANSI-map:

GetAnsiToUnicode – returns the code point for a specified map element.

// MapIndex - must be an integer between 0 and 255 
CodePoint = GetAnsiToUnicode( MapIndex )  

SetAnsiToUnicode – updates the code point for a specified map element.

// MapIndex - must be an integer between 0 and 255
// NewCodePoint - integer value of the code point to set
Call SetAnsiToUnicode( MapIndex, NewCodePoint )

UTF8 comparison method

When comparing two characters we first need to find a “sort index” for a character which is determined as follows:

  • Get the code point value for the character being compared.
  • Look in the ANSI-map using the low byte value of the code point as the index. If the value at that position is the same as the character code point then the sort index is set to that index and it is marked as “found”.
    • E.g. If the character has a code point value of 458 (0x1CA) then it’s low-byte value is 202 (0xCA). If the ANSI-map contains the value 458 at index 202 then the sort index is set to 202 and it is marked as “found”.
  • Otherwise, scan backwards through the ANSI-map looking for an element that has the same value as the code-point for the character. If we match it then the sort index is set to the same position and it is marked as “found”.
// Pseudo-code
dim ansiMap( 255 )

sortIndex = -1 ; // Not found
codePoint = seq( ch )
testIndex = bitAnd( codePoint1, 0xFF )
if ( ansiMap( testIndex ) == codePoint ) then
   // Found
   sortIndex = testIndex
end else
   // Not found
   for testIndex = 255 to 0 step -1
      if ( ansiMap( testIndex ) == codePoint ) then
         // Found and exit loop
         sortIndex = testIndex
      end         
   next
end

Once this has been done for both characters we use the following comparison procedure:

  • If one of the characters is marked as “not found” and the other as “found”, the latter is always sorted before the former.
  • Otherwise we now proceed in a manner similar to the ANSI comparison:
    • If we are using a collation sequence the sorting value for each character is extracted from the appropriate sequence using the sort index we determined above.
      • E.g. if the sort index was 202 then the sort value for the comparison is the value of the byte at position 203 (1-based) in the sequence.
    • If we are using a case-insensitive comparison without a collation sequence the two sort indexes (not values!) are masked with 0xDF and compared.
    • If we are using a case-sensitive comparison without a collation sequence the two original code-point values are compared.
// Pseudo-code
begin case
   case ( sortIndex1 == -1 ) and ( sortIndex2 == -1 )
      // Both Non-ANSI-mapped - use a simple code point compare
      cmpVal = codePoint1 - codePoint2
   case ( sortIndex1 == -1 )
      // sortIndex2 was found in the ANSI map so it's sorted lower
      cmpVal = 1
   case ( sortIndex2 == -1 )
      // sortIndex1 was found in the ANSI map so it's sorted lower
      cmpVal = -1
   case OTHERWISE$
      // Both are ANSI mapped
      begin case
         case hasCollationSequence
            sortVal1 = seq( collationSequence[sortIndex1+1,1] )
            sortVal2 = seq( collationSequence[sortIndex2+1,1] )
            
         case isCaseInsensitive         
            sortVal1 = bitAnd( sortIndex1, 0xDF )
            sortVal2 = bitAnd( sortIndex2, 0xDF )
            
         case OTHERWISE$
            sortVal1 = codePoint1
            sortVal2 = codePoint2
            
      end case
      
      cmpVal = sortVal1 - sortVal2
      
end case

So, this system works pretty well out of the box for languages that can be expressed using the ANSI character set, but for other languages much of the burden falls on the application developer to maintain and tune the language settings and collation sequences to their requirements. This could require considerable effort and ignores much of the functionality provided by the OS itself, so in the next post we’ll take a look at how this is being addressed in the next release.

String comparison in OpenInsight – Part 1 – ANSI Mode

Developers with systems that require Unicode processing will be please to know that the next release adds some new and much-needed string comparison functionality, to which we have given the super-catchy title of the “Linguistic String Comparison Mode”. However, before we get into the details of that, it’s worth taking a look at how string comparison is currently handled in the system as it has a huge effect on how your data is sorted, and it seems to be one of those murky and little understood areas which includes arcane terms like “collation sequences” and “ANSI Maps”.

(Note that we’ve included some Basic+ pseudo-code examples in this post to illustrate more clearly how some parts of the comparison routines work. These are simplifications of the actual C++ internal functions and not actual code from the system itself.)

String comparison in ANSI mode

String comparison in ANSI mode (i.e. where every character has a value between 0 and 255) is usually a straightforward exercise of comparing the byte value of characters against each other. However, it is possible to customize this using the classic ARev technique of “collation sequences”, which allow a developer to assign a custom weighting, or “sort value”, to a specific character when it is compared against others.

Collation sequences are contained in records in the SYSENV table with a prefix of “CM_”. By default OpenInsight includes the following “CM_” records:

  • CM_ANSI
  • CM_ISO
  • CM_US

A collation sequence may be attached to a language definition (“LND”) record by specifying the “CM_” key in field <10>, and when you load the LND record the collation sequence is loaded too. For example, in the German LND record (LND_GERMAN_D) you will see CM_ISO has been specified like so:

SYSENV – LND_GERMAN_D

Each CM record may contain one or two fields, each field containing a block of 256 bytes that form a collation sequence:

<1> Case-sensitive sequence (required)
<2> Case-insensitive sequence (optional)

To give a character a weighting simply find it it’s 1-based index in the sequence, and enter the byte value you want to give it instead. For example, if we wanted to give the “3” character a sort value of “87” then we do the following:

  • Find the ANSI byte value of the character “3”, which is 51 (0-based value).
  • In field<1> we replace the character at index 52 (1-based value) with a character that has the ANSI byte value of 87, which is “W”.

Points to note:

  • The first field (sequence) in a CM record is required, while the case-insensitive sequence is not – the system will use a default method for case-insensitive comparison as discussed below.
  • A collation sequence must be 256 characters in length. If not the system will not use it.
  • The last two characters in the sequence (255 and 256, 1-based) are always set to the byte values of 254 and 255 (@fm and @rm).
  • Some LND records have data in field<11> – this is for historical reasons and may be ignored.

So, now that we know what collation sequences are, we can see how the system uses them at runtime when it needs to compare strings values.

ANSI comparison when using a collation sequence

If a collation sequence has been specified (for either a case-sensitive operation, or a case-insensitive operation) the system uses it to extract the sorting value of the character being processed:

  • Get the ANSI byte value of the string character being compared.
  • Use the ANSI byte value as an index into the appropriate collation sequence.
  • The sorting value for the string character is the byte value at that index.
    • E.g. The character “3” has an ANSI byte value of 51 – using this as an index we get byte 52 (1-based) from the collation sequence – the value of that byte is the sorting value.
// Pseudo-code
ansiVal1 = seq( ch1 )
sortVal1 = seq( collationSequence[ansiVal1+1,1] )

ansiVal2 = seq( ch2 )
sortVal2 = seq( collationSequence[ansiVal2+1,1] )

cmpVal   = ( sortVal1 - sortVal2 )

ANSI case-insensitive comparison without a collation sequence

Case-insensitive comparison of two characters without a collation sequence uses the classic ASCII-style technique of bit-masking both character ANSI byte values with a value of 0xDF and then comparing the results.

// Pseudo-code
sortVal1 = bitAnd( seq( ch1 ), 0xDF )
sortVal2 = bitAnd( seq( ch2 ), 0xDF )

cmpVal   = ( sortVal1 - sortVal2 )

ANSI case-sensitive comparison without a collation sequence

Case-sensitive comparison of two characters without a collation sequence simply compares the ANSI byte value of the characters against each other.

// Pseudo-code
cmpVal = ( seq( ch1 ) - seq( ch2 ) )

That concludes this look at ANSI string comparison – in the next post we’ll take a look at how string comparison is handled in UTF8 mode, which is, as you might expect, a little more complex.

Implementing a Dropdown Panel dialog

As mentioned in a previous post we added a new “dropdown-panel” dialog to the Database Toolpanel to allow the user to edit the viewing options:

Database Toolpanel Options dialog

Whilst this looked like a fairly simple job it did require a little bit of effort to get it right so we thought we’d share the method with you in case you wished to implement something similar in your own applications.

As this was dropdown panel we wanted it to behave as close as possible to the way that a dropdown list from a ComboBox works, i.e:

  • It appears when a “parent button” is clicked
  • It should appear underneath the parent button, right-justified
  • The parent button should have a pressed appearance while the panel is displayed, and return to it’s normal state when the panel is closed
  • The panel should close when it loses the focus (i.e. it becomes inactive)
  • The Database Toolpanel is updated as each CheckBox is clicked, so there are no “OK” or “Cancel” buttons for this dialog.

So, this seemed to be a fairly straightforward task:

  • On the Database Toolpanel:
    • The parent button’s CheckStyle property was set to True so that it behaves like a CheckBox and stays pressed when clicked, and then unpressed when clicked again.
    • A “SyncButton” OMNIEVENT handler was created for the Database Toolpanel that simply synchronizes the Check property of the button to True if the panel dialog exists, or False otherwise.
  • On the panel dialog:
    • The name of the parent button is passed to the dialog, and in the CREATE event it sets it’s own SYSTEMSIZE property relative to the SYSTEMSIZE of the parent button as required.
    • The INACTIVATED event closes the dialog.
    • When the dialog closes it posts the “SyncButton” OMNIEVENT to the Database Toolpanel

The problem

At first glance it appeared to work fine: the dialog appeared, you could update the view options, and when you clicked away from the panel it closed. Bravo.

The one remaining problem however, is that many users will click the parent button to close the dialog as well as open it – it’s just a natural action. In our case this actually had the effect of showing the dialog again, because the event sequence looked something like this (remember, by default all events in OpenInsight are executed asynchronously):

  • The user clicks the mouse button (down) on the parent button
  • The dialog deactivates and raises an INACTIVATED event
  • The INACTIVATED event executes and closes the dialog, which in turn posts a “SyncButton” OMNIEVENT to the Database ToolPanel
  • The “SyncButton” OMNIEVENT executes and, because the dialog no longer exists, it sets the parent button’s Check property to False
  • The user releases the mouse button (up) on the parent button raising a CLICK event and setting the Check property to True
  • The CLICK event executes and reloads the dialog again because the parent button’s Check property is True

The solution

To solve this we needed a way to jump into that sequence so we could set a flag to control how the synchronization logic behaves – we needed to stop it setting the parent button’s Check property to False if the user had clicked on it.

The solution was to use the parent button’s BUTTONDOWN event, because this runs after the INACTIVATED event, but before the OMNIEVENT, and before the mouse-up click changes the Check property. We made the following three changes:

  • In the BUTTONDOWN handler we set a user-defined property (“@_BUTTONDOWN”) on the parent button to True. This is just to flag the fact that the button was clicked on by the user.
  • In the “SyncButton” OMNIEVENT we check “@_BUTTONDOWN” – if it’s True then we don’t do anything else, otherwise we set parent button’s Check property to False (This means that if the user clicks away from the dialog it closes as normal).
  • In the CLICK event we set “@_BUTTONDOWN” back to False, then look at the parent button’s Check property. If it’s False we don’t do anything, but if it’s True then we reload the dialog.

With these changes the dropdown panel dialog now behaves in a familiar fashion, and closes correctly regardless of how it loses the focus.

Resizing the RevEngine window

The RevEngine window can sometimes be a helpful tool during the development process, especially when you’re trying to determine just what commands the engine is actually executing, but unfortunately it’s always been a fairly small fixed-size window which can result in a lot of tedious horizontal scrolling when trying to view the contents.

As there’s only so much H-scrolling a person can take, we finally bit the bullet for the next release and made the window resizable instead, so now you can change it to something more comfortable to use as illustrated here:

The incredible resizable RevEngine window

(As an added bonus it’s also DPI-aware so will scale properly to different monitors as needed)

Of course, the RevEngine window is normally not a part of the system that we expose to our users because their usual reaction is to try and close it, and this generally tends to hang or crash the application. In order to mitigate this we also included one more change – if you click the close button the engine will not terminate – it will simply become hidden instead, so even if your users do see the RevEngine window and attempt to close it, they will not crash your application (they will just have to try harder – you know what they’re like… )

However, if you do need to show or hide the RevEngine window in your system it’s as easy as this:

Declare Function GetEngineWindow
$Insert msWin_ShowWindow_Equates

Call MsWin_ShowWindow( GetEngineWindow(), SW_SHOWNORMAL$ )

Hopefully this update should make your applications a little more user-proof and make some development tasks a little easier.

The IMAGE Object API (Redux)

Once upon a time, in a blog post far far away, we looked at a very early incarnation of the version 10 Image API which described how images were used with OpenInsight forms and controls. Since then the API has changed quite a bit, so this post gives you a PDF link to a preview of the API documentation which covers the capabilities in the next release, some of which hasn’t been described before.

An overview of the Image API

Many GUI types managed by the Presentation Server support images in one form or another; some have background images, some have glyphs, some have image lists and so on. However, many of these images have properties of their own, and in most cases these image properties are common to all.

Because of this most Presentation Server types that support images expose them as intrinsic “sub-objects”, thorough a model we refer to as the “Image Object API”. The image object’s lifetime is managed by the system and it cannot be programmatically destroyed from Basic+, but it can be manipulated by the Get/Set_Property and Exec_Method functions just like any other Presentation Server object.

The entire Image API is described in the linked PDF below but be aware that not all image objects support all of the API. For example, a PUSHBUTTON type does not support an “INDEX” property for its background image, and its SplitGlyph image doesn’t support an ALIGN property: All such exceptions are documented in the relevant sections that describe each object type.

Supported image types

The Presentation Server supports the following image types via the WIC (Windows Imaging Component) sub-sytem:

  • BMP
  • ICO
  • PNG
  • GIF
  • JPEG
  • TIFF

Image Object Properties

NameDescription
ALIGNSpecifies the horizontal and vertical alignment of the image
within its parent.
AUTOSCALESpecifies if the image should be scaled along with its parent
object.
COLORKEYSpecifies the color in the image that should be treated as the
“transparent color”.
COUNTSpecifies the number of sub-images within an image file.
FILENAMEReturns the name of the image file being displayed.
FILENAMESSpecifies an array of DPI-specific image files to display.
FRAMECOUNTReturns the number of “frames” within an image.
FRAMEDELAYReturns the delay time in milliseconds for a multi-frame image.
FRAMEINDEXSpecifies the frame to display within a multi-frame image.
INDEXSpecifies the index of the sub-image to display within a multiimage file.
OFFSETSpecifies the point within the image (not the object) to begin
drawing from.
ORIGINSpecifies the point within the object (not the image) to begin
drawing from.
SIZEReturns the width and height of the image in pixels
STYLESpecifies how the image is drawn into an object (Tiled,
Stretched, Clipped or Scaled).
TRANSLUCENCYSpecifies the degree of transparency applied to an image when
it is drawn

Image Object Methods

NameDescription
SAVETOFILESaves the current image to a file.
SETHBITMAPLoads an image from a Windows BITMAP handle (HBITMAP).
SETIMAGELoads an image from an array of “raw” image bytes.
SETREPOSIMAGESpecifies the image file(s) using an OpenInsight repository
IMAGE entity.

Here’s the link to the Image Object API PDF – adding animated GIFs to your applications has never been so easy!

Databinding in the Form Designer

Still on the topic of UI updates in the next release we also took a look at the Databinding dialog in the Form Designer, which had been in need of a make-over for some time. Based on user feedback we wanted to provide much more information when selecting columns and so applied this to the redesign, which now looks like this:

Updated Form Designer Databinding dialog
Redesigned databinding dialog

As you can see, there’s quite a few changes so here’s a brief description of the new features:

  • Typing in the Table box uses AutoComplete to provide a matching list of tables
  • Typing in the Column box auto-selects the first match in the list below
  • The list of columns includes the field position as well as the name
  • The list of columns can be sorted from options on the context menu:
Databinding context menu
Databinding context menu
  • Columns can be filtered by type (F or S)
  • Columns can be filtered so that only multi-valued columns are shown (this is the default for columns bound to an EditTable control)
  • Use the “No Column Selected” option to clear Databinding information from a control
  • The dialog is resizable and remembers the size between calls.
  • Summary information for each column can be seen in the grid control on the right (This is view-only and cannot be used to edit the details – this may change in a future update, but not this one).
    • Description, Formula, Validation and Heading can be expanded to see their full details
Databinding dialog and formula details
Databinding and expanded formula details

Hopefully this will provide a welcome improvement to working with data-bound forms in the next release.

LocateC, GSClear and Expendable

The next release of OpenInsight sees some updates to Basic+ with the addition of two new statements and the return of an old Arev compiler keyword.

The LocateC statement

A counterpart to the well-known IndexC function, the LocateC statement simply performs a case-insensitive “locate” operation on a string, using the normal Locate statement syntax like so:

LocateC substring In string Using delimiter Setting pos Then/Else

The GSClear statement

This statement clears down the internal GoSub return-stack of the currently executing program, so that a subsequent Return statement will return to the calling program rather than jump back to an originating GoSub statement as normal. This is usually used to handle severe error conditions where an “early return” to the caller is desirable and there is a need to pass a value back to the caller. E.g:

Compile Function Test( Void )

   GoSub DoTheThing

Return "The Thing Was OK"

DoTheThing:
   
   GoSub CheckTheStuff

Return

CheckTheStuff:

   If TheStuffIsReallyBad Then
      // Return directly to the caller
      GSClear 
      Return "The stuff is really bad!!!"
   End

Return

This is basically the same as performing an Abort operation, but allows you to return a value, which Abort does not.

The Expendable statement

Marking a program as “expendable” was a useful feature in Advanced Revelation that instantly removed a program from the cached program array after it had finished executing. This was very useful in networked development scenarios where programs being edited could be loaded by different workstations – they could still get an updated version without having to restart the application or issue a manual GarbageCollect statement to clear the cache. This is a similar scenario to using a current tool like the OpenInsight EngineServer, where individual engines can cache programs during development – it can become tedious to force them to load an updated version after making changes.

To mitigate this the Expendable keyword has been reintroduced and is simply added to the program header declaration like so:

Compile Expendable Function( Param1 )
   // Do stuff
Return RetVal

or, if you don’t use the optional “Compile” keyword:

Expendable Function( Param1 )
   // Do stuff
Return RetVal

With this the engine now deletes the program from the cache after all references to it have been removed from the call stack, forcing it to reload from disk the next time it is called.

A tour around the updated IDE Database Toolpanel

Contrary to popular belief, or indeed the evidence of your own eyes, this blog is still actually alive, so in the next few posts we’ll try and look some of the work that’s been going on behind the scenes for the next release. In this post we’ll take a quick tour around the IDE Database Toolpanel that’s had a much-needed face-lift to improve it’s usability:

New Database Toolpanel
Updated IDE Database ToolPanel

As you can see it looks a little different now – we’ve updated the artwork, and you can see that different table types have different icons:

  • Dictionary tables have a purple book overlay marker
  • Index tables have a green book overlay marker
  • The different data-source types (RTP57, SysColumns, DSBFS etc) have their own icons

We’ve also changed the layout and size of the toolbar buttons so there are five that fit much better into the IDE. From left to right these are:

  • New table
  • Add table
  • Alias table
  • Save database definition
  • View options

The View options button loads a dialog as a “dropdown panel” that defines what you see in the table list:

Database Toolpanel View Options

Most of these are self evident, but the “Group by table name” option is new – it simply controls how tables are sorted in the list – when the box is checked the data, dictionary and index tables appear together, when unchecked they appear in proper ascending alphabetical order (Implementing this dialog as a “dropdown panel” is an interesting exercise in it’s own right and we covered this here).

The filter box remains the same – simply start typing to restrict the list to those table names starting with the same string:

Database Toolpanel Filter

We’ve also given the context menu an overhaul, both visually and in terms of making it more context aware based on the table that you right-click on (only enable index options for index tables, verify options for LH tables and so on):

Database Toolpanel Context-menu

Underneath the hood there are a few other changes – including how this toolpanel is updated based on changes to the database, and when warnings about saving the database definition are given. Hopefully you’ll find using this part of the IDE easier in the next release.