Home page Home page Home page Home page
Pixel Header R1 C1 Pixel
Pixel Header R2 C1 Pixel
Pixel Header R3 C1 Pixel
By Sprezz | Tuesday, 5 September 2023 12:57 | 2 Comments

 On one of our more recent projects, we were required to display a PDF document by double-clicking in an edit table. No real issue, we can use just use SHELLEXEC as documented here right?


The problem with a SHELLEXEC, is that by default, it launches a browser that has an address bar  containing the full URL to the document in question, allowing the user to modify the URL and potentially access information they were not privy to. (The users don't have browse rights so there isn't a problem of them browsing to other information, but knowing the fully formed URL they could take intelligent guesses at other potential document names). So, we considered looking at turning the address bar off, but then remembered the new WebView control. A control that allows you to embed the Microsoft WebView2 Edge Browser directly into an OpenInsight form and to interact with it.

This looked like it was going to be the easiest hack ever. Create a form with just two controls, the WebView control and a close button. Have it accept as a create parameter the document to display, and all is good, yes?

So we added a quickevent to the form that simply set the URI property of the WebView control, to the value passed in the createParam for the form. 

It ran.

It did nothing.

Puzzled we modified the CREATE quickevent to also update the screen caption with the file name we were trying to display. (This ability to stack quickevents rocks!). 

The caption changed to the correct value but still nada.

Thinking laterally we invoked the system monitor and used the SP command to set the URI of the control to the value we had passed in. 

It displayed.

Rinse and repeat.

Chatting to the developers, it seems the issue here is that the WebView control takes a little time to instantiate itself, so it it might not be finished before the CREATE event runs. So we have to wait for the WebView control to be finished and THEN set the URI.

Sounds like a case for looping, querying status and waiting, yes?


The WebView control is asynchronous. So all we have to do is wait for an event that tells us that the WebView control is ready, and THEN set the URI.

All this took was two quickevents. 

One on the CREATE event, setting a synthetic property to contain the document name

and one on the WEBVIEWCREATED event for the WebView control (which is fired when the WebView is fully instantiated) which retrieves the synthetic property and uses it to set the URI property.

The results achieved what the client wanted. A displayed document with no identifying file paths. And no code. 
By Sprezz | Tuesday, 1 August 2023 15:59 | 0 Comments

 Occasionally RTI slip out little tools and utilities that they've found useful. If you're very lucky they'll even ship source. One such gem in OI 10 is the OIWIN Date/Time picker - RTI_DATETIME_POPUP to its friends.

If ever you've wanted to offer your user a way of selecting date or time from a calendar/time control, and you've not been too happy with the datetimepicker OCX and the old POPUP_MONTH dialog seems a little dated, then this is the one for you.

It really is stunningly simple to use. Just add a quickevent to call a stored procedure and you're sorted.

For illustrative purposes, here's a sample window -

The cuebanners reflect the conversion on the edit line. The quickevent for the options on each editline is defined thusly - 

Once you've chosen the Target you can just accept the defaults other than DEFPROP which should be replaced with INVALUE.

We only need one options button because we're taking advantage of a newly exposed property - in the property panel under the Behavior section, we're setting ALLOWFOCUS to false and then we can just send an OPTIONS event to @FOCUS.

So how does the routine behave? See the following screen shots -

Now there are a couple of caveats that I need to mention. The first (as you can see) is that DT and DTS provide the same dialog. The second (due to a bug that I leave you to find) is that DTS always returns 1967 as the year. But as you are encouraged to save under a different name and compile your own version it is easily fixed! If you don't want to then it is all fixed with the latest beta.

The examples given below show what displays when you let the system determine default values. Examining the source reveals that, should you wish to, you may have much more granular control over the behaviour of the control. In this endeavour RTI_POPUP_DATETIME_EQUATES are your friend, but to save you having to look at this, we'll document their use here.

If you use DIALOG_BOX to launch the RTI_POPUP_DATETIME window, you can pass in an @Fm delimited create parameter having the following values -

    <1>    Mode, D, DT, DTS, MT, MTS
    <2>    Initial date to use in internal format
    <3>    Initial time to use in internal format
    <4>    Parent
    <5>    Horizontal alignment against owner (L or R)
    <6>    Value mark delimited array containing the screen size of the owner
    <7>    First year in the year dropdown
    <8>    Last year in the year dropdown

We can override date ranges, set default values and even alter dialog positioning by passing in the correct values.

By Sprezz | Tuesday, 11 July 2023 12:17 | 0 Comments

One of the biggest differences between OI TCL and AREV TCL is that the latter made use of the VOC file to allow the user to customise commands and even to add their own commands. This does not seem to be the case in OI, rather a cursory examination of the object code for RTI_IDE_TCL suggests that the command list is hard coded. 

With this in mind it might be useful to document exactly what commands are supported, and where unclear, what they actually do. For the purposes of this article it will be assumed that the reader is familiar with the syntax of AREV TCL commands. When optional variables are nominated they will normally be in the order TABLE ROW(S) OPTIONS.

So in alphabetical order, the commands are as follows - 

.LDisplay a popup of previously executed commands.
.nCopy the nth command to the command line.
ASSISTANTLaunch the Assistant popup.
Attaches a table.
Clears the active select list. Optionally if a name is specified, deletes that list.
Clears the nominated table.
Copies rows from one place to another. Can use an active select list.
Launch the CopyTable dialog ignoring any command line parameters.
Counts the rows in the nominated table with optional selection criteria
Deletes the nominated rows from the nominated table, or uses an active select list against the nominated table, or launches a dialog.
Deletes the nominated list.
DICTEither launches the new table dialog or if a table name is specified, opens the dictionary of that table in the IDE.
EDITLaunches the appropriate tool to edit programs or record depending on the nominated table. Will accept a list of keys or an active select list. Particularly useful with FIND (q.v.).
EVALCompiles and runs the statement
FINDFind the nominated string in the nominated table. Produces an active select list.Takes Option (G to produce a grid and (X to produce a list. Useful in conjunction with EDIT.
Activates the nominated select list.
LISTRuns the LIST statement.
List the nominated program to screen. Only works with SYSPROCS and requires uppercase row id.
LISTDICTLaunches the LISTDICT dialog prepopulated with any nominated table.
Lists the tables.
Lists all indexed tables or the indexes for the nominated table.
List the contents of the nominated volume.
List all volumes.
Currently not implemented.
Launches the MAKEVOC dialog.
Names the volume at the nominated location with the optionally nominated name. In the absence of a name a default date time will be used.
OFFCloses OI.
PAINTOpens the form designer for the nominated window.
Allows selection and activation of a saved list from the query table.
RUNRuns the nominated program passing the nominated parameters.
RUN_REPORTDisplays the run report selection dialog OR actions the passed report name, list command, file info comma delimited.
Saves the active select list under the nominated name.
Performs the nominated select/explode-on.
SET_MFSLaunches the SET_MFS dialog.
SUMSums the nominated column in the nominated table.
WHODisplays the WHO window.

By Sprezz | Tuesday, 27 June 2023 18:42 | 0 Comments

 Recently we were stumped by a compilation error when using pipes to create a multi line statement, this sort of thing,

    if value then

        on inlist("ALPHA,BRAVO,CHARLIE", value, ",") goSub |

            doAlpha, |

            doBravo, |



the compiler just kept complaining about an unmatched then/end else. We thought that the only way to get to the bottom of this was to look at what the precompiler produced to see where the error was. Struggling we tried to recall the syntax of the pragma to save the precompiled output. 

Fortunately a more knowledgeable team member pointed out that using the pipe stripped spaces by design, to allow aesthetic alignments of strings. What this meant was that the compiler was not compiling goSub doAlpha, but rather, goSubdoAlpha.

The solution, repeating pipes. Using two pipes prevents the spaces being stripped and 

    if value then

        on inlist("ALPHA,BRAVO,CHARLIE", value, ",") goSub ||

            doAlpha, |

            doBravo, |



compiled just fine. 

Anyway it's been 16 years since our article on using pragmas and it's still as relevant as ever, so why not check it out and refresh your memory!

By Sprezz | Wednesday, 1 March 2023 17:42 | 0 Comments

We recently came across a situation that we have literally never encountered in our decades of working with Linear Hash. Basically a FIXLH operation was interrupted mid process and hi-jinks ensued. The resultant mess took a while to sort out but our findings may prove useful if you ever find yourself in a similar situation.

This specific issue was encountered on an AREV 3.12 system but the logic for FIXLH remains roughly the same so the information remains applicable.

Basically, the FIXLH operation was interrupted about 10% of the way through processing (the NTVDM abended). A quick check of the table header revealed that it believed itself to be a clean empty table with no rows. Yet listing the table produced a set of row ids after an initial pause while nothing was apparently happening.

What had happened was that the table header had been reset to that of an empty table, and the first 10% or so of the primary frames had been blanked. This caused us to investigate the processing undertaken by FIXLH and we can confirm that it is as follows:

    Create and/or clear the temporary dumpfix table

    grab frame zero group

    write rows in group to temporary dumpfix table

    set frame zero header to that of an empty table


        grab next group

        write rows in group to temporary dumpfix table

        wipe primary frame to null

    until no groups left


    copy rows in temporary table back to source table

So if the operation is interrupted, some of the data will be in the source table and some will be in the temporary dump fix table. 

As an additional caveat, note that if the rows in the temporary dump fix table are not stored off elsewhere, they will be lost next time FIXLH is run.

Since the advent of the UD we've never had to actually fix a GFE for quite some time, so this was an eye-opening operation!

Backups are your friend...

By APK | Tuesday, 31 January 2023 16:04 | 0 Comments

Recently at a client site, we encountered an error that was a little more baffling than normal.  Under very specific circumstances,    RLIST would bail with no results.  Eventually, we had a duplicatable test sentence which was basically


When we removed the BY clause (CALC_COL_1) our sentence ran to completion.   This put the problem squarely on CALC_COL_1, which is where we started debugging.  

We changed CALC_COL_1 to a simple “@ANS=1” and ran the sentence again.  This worked correctly.  To prove to ourselves that CALC_COL_1 was the problem, we put back the original code and changed our sentence to 


This also worked.   

Now we’re starting to get a little confused.  Modifying CALC_COL_1 solved the problem.  Replacing CALC_COL_2 also solved the problem.  It has to be some sort of interaction with the calculated columns, because there’s no obvious consistency lining up the errors with the sentence structure.

It’s time to break out the profile log and see if something jumps out, and it did.  Something appeared off with EXTERNAL.SORT.  It seems like it executed a few times, and then everything stopped.  We dusted off our copy of PROCMON.EXE 1 and ran the process again.  PROCMON showed that the sort file was being opened, but that it was not being closed.  It also showed a lot of file access  We’re now convinced this has something to do with sorting in general, and not with the specific field being sorted.

After trying well over a dozen different variations on the RLIST sentences, we finally worked out that we needed two things to happen for the error to occur.  The first was that we had to have CALC_COL_1 as the sort column.  The second was that the WITH clauses needed to return enough keys to force the system into using EXTERNAL.SORT instead of a simple in-memory sort.

Normally, when OpenInsight starts behaving very oddly, the culprit is usually a function that’s not clearing set_status() before calling get_status.  When that happens, the get_status() call could be returned an error from 10 programs back.  We reworked the calculated columns to ensure we clear set_status, and feeling very proud of ourselves, sat back and waited to bask in the glory offered to us by our client.  Obviously, the report failed again.

Sitting back, we start wondering if maybe there’s a small part of the RLIST code that’s still thinks it’s Advanced Revelation and it’s actually checking the pseudo STATUS() variable.  So, we reworked the calculated columns to ensure we clear set_status and status(), and feeling very proud of ourselves, sat back and waited to bask in the glory offered to us by our client.  Obviously, the report failed yet again.
Surely there can’t be three ways to set an error condition in OpenInsight?  It turns out that there is.  We suddenly remembered @FILE.ERROR.  So, we rework the calculated columns to ensure we clear set_status and status() and @FILE.ERROR, and feeling very proud of ourselves, sat back and waited to bask in the glory offered to us by our client.  And this time there was much rejoicing and basking.

With the solution in hand, we set out to find out exactly why this happened and where the system was registering the failure.  What we found was the calculated field was doing a lot of file access and would attempt to access records that didn’t exist.  This was what was setting @FILE.ERROR.  But, we hit a two-fer, as they say.  Some of the randomness had to do with the amount of data returned.  If enough sort data was returned the system would call EXTERNAL.SORT to sort the data.  EXTERNAL.SORT writes to temporary OS files, and the OS file commands use @FILE.ERROR to check for errors.  EXTERNAL.SORT would execute an OS operation, check @FILE.ERROR, find the pre-existing error, and abort the process.  But, this would only happen if the very last row processed set @FILE.ERROR.  Otherwise RTP57 would clear any prior @FILE.ERROR settings, and @FILE.ERROR would be null during the EXTERNAL.SORT call. 

1.  PROCMON and other useful tools are available as part of the Sysinternals suite of development tools, which no programmer should be without.



By Sprezz | Thursday, 12 January 2023 12:46 | 13 Comments

Those of you who've been in the industry for a while will remember the smugness with which we treated the Y2K issues the rest of the world were experiencing. It didn't affect us, we stored our dates in internal numerical format. I'm sure we all have favourite anecdotes. Mine was being called into the Press Office of a major Government department to certificate their AREV tracking system. I sat down and gave the system a cursory glance. "Ermmm - it doesn't seem to use dates?". "No, it doesn't". "Well, here's your certificate"... turns out they couldn't get the budget for system tweaks but they could for Y2K compliance. I spent the rest of the day implementing their desired changes.

Anyway, this week wiped the smile off of our collective faces (if you'd ignored the advice in KB 42 thirty or so years ago - which to be honest you could be forgiven for) with the advent of our very own variant - we'll call it the 20100 bug, although it's not a bug, it's an unfortunate feature.

Users began reporting that date searches were failing for values after the 10th of January 2023. At first we couldn't see an obvious reason. We built a database containing date values going back to the previous century with five rows per date and indexed it. We wrote a test program and ran it

RUN ZZ_POP 10/01/2023

RUN ZZ_POP 11/01/2023

RUN ZZ_POP 12/02/2023

But then a throwaway remark from Martyn about pivot dates being discussed in the MV community led to a realisation. What if we used internal dates?

RUN ZZ_POP 20099

RUN ZZ_POP 20100


What we've fallen foul of is discussed in the KB reference earlier. Basically BTREE.EXTRACT takes what it is given and tries to ICONV it to use for the look up. If it can't ICONV it (20099 isn't a viable date) it uses the value passed. If it CAN ICONV it, is uses the ICONVed value. And guess what? 20100 ICONVs to the 2nd of January 2000 as you can see above.

Of course, this won't be the first time this has happened in the wild - for example looking for the 7th of December 1998 using 11299 would have returned the 1st of December 1999 and so on.

Note that the same issue will be experienced when using internal date formats with RLIST statements.

The solution? When calling BTREE.EXTRACT use EXTERNAL values as it will try and ICONV the data before using it.


We've been asked to share the code we used to locate btree.extracts in client systems to enable a manual inspection to determine possible failure points. This is a rough and dirty hack which met our requirements. Feel free to tailor to your own requirements.

0001  Subroutine zz_find_Btree_Extract( void )
0002  /*
0003     Author      AMcA
0004     Date        Jan 2022
0005     Purpose     Quick hack to help identify system usage of btree.extract
0006                 Provided as is with no warranty
0007  */
0009     Gosub initialise
0010     Gosub process
0012  Return
0014  initialise:
0017     columnsToCheck = ",,8"
0019     loopCtr        = Dcount(filesToCheck, ",")
0020     resultString   = ""
0022  Return
0024  process:
0026     For loopPtr    = 1 To loopCtr
0027        file        = Field( filesToCheck, ",", loopPtr )
0028        column      = Field( columnsToCheck, ",", loopPtr )
0030        resultString := file : \0D0A\
0032        if file = "SYSTABLES" Then
0033           starting = "%"
0034           Gosub processSysTables
0035        End Else
0036           starting = "@"
0037           Gosub processRest
0038        End
0040        resultString := \0D0A0D0A\
0042     Next
0044     Oswrite resultString On "ZZ_BE.TXT"
0045     Call Set_Property("CLIPBOARD", "TEXT", resultString )
0047  Return
0049  processRest:
0050     Open file To v Then
0051        Gosub processV
0052     End Else
0053        Call FSMsg()
0054     End
0055  Return
0057  processV:
0059     Select v
0060     eof = 0
0061     Loop
0062        Readnext id Else eof = 1
0063     Until eof Do
0064        If id[1, 1] = starting else
0065           Read row From v, id Then
0066              ptr = 1
0067              If column Then
0068                 saveRow = row
0069                 row = row< column >
0070                 Convert @Vm To @Fm In row
0071              end
0072              lenRow = Len(row)
0073              lineNo = 0
0074              If lenRow > 0 then
0075                 Loop
0076                    nextline = row[ptr, @Fm]
0077                    ptr = Col2() + 1
0078                    lineno += 1
0079                    there = IndexC( nextLine, "btree.extract(", 1)
0080                    If there Then
0081                       variable = Trim( nextLine[ there + 14, ","] )
0082                       If variable[1, 1] = "'" Or variable[1, 1] = '"' Then
0083                          // passing literals - check the line
0084                          resultString := file : \09\ : id : \09\ : lineNo : \09\ : nextLine : \0D0A\
0085                       End else
0086                          // ok we're now going to work back through the code until we find = for our var
0087                          tempLineNo = lineNo
0088                          Loop
0089                             tempLineNo -= 1
0090                             line = row< tempLineNo >
0091                             If Trimf( line )[1, 1] = "*" Then
0092                                line = ""
0093                             End
0094                             Convert " " To "" In line
0095                             line  = " " : line
0096                          Until IndexC( line, " " : variable : "=", 1)
0097                          Until tempLineNo = 0
0098                          Repeat
0099                          If tempLineNo = 0 then
0100                             resultString := file : \09\ : id : \09\ : lineNo : \09\ : "Not found " : row< LineNo > : \0D0A\
0101                          End else
0102                             resultString := file: \09\ : id : \09\ : lineNo : \09\ : row< tempLineNo > : \0D0A\
0103                          end
0104                       end
0105                    End
0106                 While ptr < lenRow
0108                 Repeat
0109              End
0110           End Else
0111           end
0112           Call send_info( file : " " : id )
0113        end
0114     repeat
0115  return
0117  processSystables:
0119     Open file To v Then
0120        Select v
0121        saveV = v
0122        eof = 0
0123        dictCtr = 0 
0125        Loop
0126           Readnext id Else eof = 1
0127        Until eof Do
0128           If ID[1, 5] = "DICT." Then
0129              Open id To v Then
0131                 file = id
0133                 Call push.Select( v1, v2, v3, v4 )
0134                 Gosub processV
0135                 Call pop.Select( v1, v2, v3, v4 )
0136                 eof = 0
0138              End Else
0139              *   Call FSMsg()
0140              End
0141              v = saveV
0142           end
0143        Repeat
0144     End Else
0145        Call FSMsg()
0146     End
0148  return
Pixel Footer R1 C1 Pixel