Home page Home page Home page Home page
Pixel
Pixel Header R1 C1 Pixel
Pixel Header R2 C1 Pixel
Pixel Header R3 C1 Pixel
Pixel
By apk | Friday, 4 May 2012 20:45 | 0 Comments

Problems in the Revelation world seem to be like buses. You don't hear about something for years, then in 2 days you receive 5 questions on the same topic. Right now, that topic seems to involve overflow frames.

We were thinking about overflow frames in regards to selects, and why some files, especially larger and more active files seem to have excessive amounts of overflow. We decided that, if you really think about it, the file is suffering from the laws of unintended consequences. Suppose you have an active file, in which you are creating new records frequently. Also suppose you are also issuing frequent non-index based selects on this file. As you know, when selects are issued against a file, the sizelock is increased to prevent the file from expanding or contracting. This means whenever a workstation is issuing a select, any new record written to the file is probably going to go into overflow, even if that write would have created a new group.

What this tells us is that large amounts of overflow in heavily used files do not indicate any problems with the hashing algorithm, but actually result from the resize hold placed against the file.
By Captain C | Tuesday, 24 April 2012 10:10 | 0 Comments
As you'll doubtless know there are several core routines used in Basic+ to interact with OI forms, probably the three most important being:

  • Get_Property()

  • Set_Property()

  • Send_Message()

To maximize performance when dealing with properties it is common practice to pass @rm-delimited arrays to Get_Property and Set_Property like so:


0001  /* 
0002     Example to illustrate accessing properties via @rm-delimited arrays.
0003      
0004     i.e to replace code like this:
0005     
0006        call set_Property( @window, "TEXT", "Customers" )
0007        call set_Property( @window, "TRACKINGSIZE", trkSize )
0008        call set_property( @window, "VISIBLE", TRUE$ ) 
0009        
0010     ... and so on...
0011  */
0012  
0013     objxArray =        @window
0014     propArray =        "TEXT"
0015     dataArray =        "CUSTOMERS"
0016   
0017     objxArray := @rm : @window
0018     proparray := @rm : "TRACKINGSIZE"
0019     dataArray := @rm : trkSize
0020  
0021     objxArray := @rm : @window
0022     propArray := @rm : "VISIBLE"
0023     dataArray := @rm : TRUE$
0024  
0025     call set_Property( objxArray, propArray, dataArray )
0026     


A recent support query from one of our clients was to enquire if the Send_Message() function also supported a similar interface, and unfortunately the answer is no - it does not accept @rm-delimited arguments when invoked.

However the question did trigger a distant memory that led me to dig up a very old email thread from many years ago between ourselves and developers at Revelation, during which a similar capability was discussed and implemented, but alas, it appears, never documented.

Whilst Send_Message() itself doesn't support @rm-delimited arrays of message data, the Set_Property() function does support a "SEND_MESSAGE" property which can be used in a similar manner - you simply wrap the message name and arguments into an @fm-delimited array and use this as the new property value to set.

e.g.


0001  /* 
0002     Example to illustrate calling messages via @rm-delimited 
0003     arrays. 
0004      
0005     i.e to replace code like this: 
0006       
0007       call send_Message( @window, "COLOR_BY_POS", colPos, rowPos, cellColor ) 
0008       call send_Message( @window, "COLSTYLE", colPos, colStyle ) 
0009        
0010    ... and so on... 
0011  */
0012  
0013     colPos    = 3
0014     rowPos    = 0
0015     cellColor = RED$ : @fm : WHITE$ : @fm : GREEN$ : @fm : YELLOW$
0016  
0017     convert @fm to @vm in cellColor
0018  
0019     objxArray =        @window : ".TABLE_1"
0020     propArray =        "SEND_MESSAGE"
0021     dataArray =        "COLOR_BY_POS" : @fm : colPos : @fm : rowPos : |
0022                        @fm : cellColor
0023  
0024     objxArray := @rm : @window : ".TABLE_1"
0025     proparray := @rm : "SEND_MESSAGE"
0026     dataArray := @rm : "COLSTYLE" : @fm : colPos : @fm : colStyle
0027  
0028     call set_Property( objxArray, propArray, dataArray )
0029     


Notice the convert statement in the code above - if you use any system delimiters in your message arguments (@fm,@vm,@svm,@tm) then you must convert them down a level, because the SEND_MESSAGE property will convert them up a level before executing the message internally.

In this way we can duplicate the stacking behaviour of Get and Set_Property for Send_Message.

Labels:

By Sprezz | Monday, 2 April 2012 13:27 | 2 Comments
OpenInsight 9.2.1 saw the introduction of a routine called RTI_DIFF which which returns an array describing the results of the comparison of the two strings passed. The question it effectively answers is "If I wanted the first string to be the same as the second string what would I have to do?". It'd probably help if you just assumed that the two strings were different versions of a program that you wanted to compare for changes - although you could use it to compare any strings - in an auditing MFS for example. Usage is simple



  resultsArray = RTI_Diff( var1, var2 )

Now before we go any further I'd like you to take a deep breath, centre your chakra and repeat the following mantra...

From exclusive, to inclusive. From exclusive, to inclusive. From exclusive, to inclusive. From exclusive, to inclusive.

OK are you still with me? Good. Keep that mantra in mind because you're going to need it to make sense of what comes hereinafter...

Strictly speaking to make it more comprehensible to OI programmers RTI could have called it RTI_SIMILARITIES_AND_DIFFERENCES but that'd suck to type every time.

The easiest way to describe the use of the function is to show it in operation. So consider two data rows having similar but different makeups :-


To highlight the differences - line 7 is MVed in VAR2, lines 10 and 11 are missing, line 17 is different, and there are two inserted lines between line 18 and 19.

Now consider the result of calling RTI_DIFF with these two variables. The resultant return is a field mark delimited array having a line for each significant fact/similarity. Each line has five multivalues as follows :-



  <0, 1> = nature of comparison
  <0, 2> = starting line number var 1
  <0, 3> = ending line number var 1
  <0, 4> = starting line number var 2
  <0, 5> = ending line number var 2

The nature of the comparison can have the values "equal", "replace", "insert" or delete".

So to illustrate with the results of our call to RTI_DIFF using the two variables above the result is



Now remember our mantra? From exclusive, to inclusive? Well let's apply it here. Remember that what we're actually asking is "What do we need to do to Var1 to make it the same as Var2?". What this result tells us is that  :-

  1. Lines 1 to 6 are the same in both and can be ignored
  2. Line 7 is different and should be replaced in string A with line 7 fronm string B
  3. Lines 8 and 9 are the same and can be ignored
  4. Lines 10 and 11 from string A should be deleted
  5. Lines 12 to 16 are the same as lines 10 to 14 (of course as we've deleted 2 lines 12 to 16 has become 10 to 14 but...)
  6. Line 17 in string A should be replaced with line 15 from string B
  7. Lines 18 and 16 are the same and can be ignored
  8. At line 18 in string A insert lines 17 and 18 from string B
  9. Lines 19 and 20 are the same and can be ignored.
Using this returned map, we can compute the differences between string A and string B. This can be useful in a wide range of situations. It can be used to compute deltas and versioning information for a source control program. It can be used to compare a current data row with the original data row as read off disk for an AUDIT.MFS routines. It can be used to compare data rows after a partial data restore, for example, after a GFE occurs and you've experience some data loss. The applications of this form of string comparison are limited only by your imagination.
By Sprezz | Tuesday, 13 March 2012 12:58 | 0 Comments
We've recently been having fun in Sprezz Towers creating an O4W app and we've been very impressed with just how much we can achieve without cutting any code. In some ways it feels a bit like the old days of R/Design with Rev G. Because we saw it in that way we had certain expectations as to how the deployed product would work which we expect most other developers will share. Our testing revealed that some of these expectations were not being met and after a series of discussions with the team at Revelation we've decided to outline what we found and to describe the workarounds and fixes that can be done and will be made available. Hopefully this will prevent other developers from falling into the same trap!

Our first expectation was related to OConvs and Iconvs on a dictionary item.


From this screen shot you'd probably assume that if the user entered say "31 Feb 2012" into the field and O4W was told to save it, it would refuse and display an error message. But in fact if you did that, not only would O4W not complain - it would actually insert "31 Feb 2012" into the database. The key here is the "Input Validation" prompt. If you don't tell O4W to validate something it won't just default back to the dictionary validation - it just won't validate.

We were surprised by this so Revelation have provided a work around in 9.3.1. If you add a PREWRITE event to your commuter program you can check the contents of a variable called STATMSG@ and if this contains anything it means that one or more of the conversions have failed. You can then simply return 0 from the commuter to cancel the save and display an error message.


   if STATMSG@ <> "" then
       * Error occurred in ICONV – abort the write
       * statMsg@ is already set, so the user will see what caused the error…
      retVal = 0
   end  else
      retVal = 1
   end

return retVal


Revelation are going to go one step further in the next major release and allow the developer to specify that the validation is against the dictionary so we won't need to cut ANY code.

The next surprise came when we set up the system in such a way that the write failed. This could be for physical reasons (say out of disk space) or logical reasons (write denied by security MFS). We expected to be told about this by an error message. Instead O4W assured us that the row was written to disk. It failed silently. Revelation have acted very quickly to fix this and 9.3.1 contains a patch which displays a message if the row cannot be saved..

So to summarise, if you're developing in current versions of O4W don't expect it to automatically validate your data nor to report back on a failed write until you upgrade to 9.3.1.
By Sprezz | Sunday, 25 December 2011 18:55 | 0 Comments
Following on from our last post on colouring individual popup lines we move onto one of the holy grails of developers - the ability to add a button to a popup and launch our own program from it - interrogating the popup to display more information about the current row for example. Using the example we showed last time - note that we now have a button on the popup called "Display"



Clicking the button results in this



To accomplish this we need to be familiar with

  • The TIMER tecnique we discussed in the last blog posting
  • The fully qualified entity ids of the individual popup controls
  • The use of Window Common
  • The structure of the Window Common used for quick events

The TIMER technique we discussed in the last blog posting
If you haven't yet read the last blog entry then you might like to before continuing.

The fully qualified entity ids of the individual popup controls
To add a display buttton to the popup we could add a new control to the popup on the fly but that is a lot more complicated than taking over a control that already exists. So the trick that we are going to use is to add a button on to the popup that we're not going to use for its stated purpose and change its display text to "Display" and its action to something more suited to our use.

For this example I'm going to hijack the "Print" button so we need to add that to the popup definition.




For your purposes you might want to use one of the other controls so for your convenience here's a list of them all :-

POPUP.CB_OK   
POPUP.CB_CANCEL
POPUP.CB_CLEAR
POPUP.CB_ALL
POPUP.CB_FIND
POPUP.CB_PRINT

Returning to our timer event - the first thing that we're going to do is to modify the TEXT of the print button to make it read "Display".

printButton = "POPUP.CB_PRINT"

objxArray := @Rm : printButton
propArray := @Rm : "TEXT" 
dataArray := @Rm : "Display"       

dataArray = set_Property( objxArray, propArray, dataArray ) 

With that done we need to go about modifying the action to take on the click.

The use of Window Common
To use Window Common you just have to add the following code to your subroutine.


   winId = "POPUP"
   $Insert oiwin_Comm_Init

If you're not familiar with Window Common then where've you been for the past 15 years?

The structure of the Window Common used for quick events
Window Common was effectively documented in detail in Technical Bulletin #2 - Compiled Window Structures, available on an old Works CD. We only need a very limited subset however so we'll deal with that.

Each control on the window has the events associated with it in the variable controlSemantics@. So we need to firstly find the control, then find the CLICK event associated with it and then remove the logic that is currently there and replace it with a call to OUR routine instead.

The quickevent structure that we need is as follows :-

quickEventInfo<0, 0, 0, 1 >  =  Code for type of event
quickEventInfo<0, 0, 0, 2 >  =  The action to take
quickEventInfo<0, 0, 0, 3>  :=  The routine to call
quickEventInfo<0, 0, 0, 4>   =  The parameters to pass

So when the popup is originally launched the labelled common looks like this


We want to modify it to look like this




The code required to do this is as follows :-


Locate printButton In controlMap@ using @fm setting gotIt then

   /*
      OK let's change the quickevent to call OUR program passing in the usual
   */

   quickEventInfo  = "R" : @Tm    
   quickEventInfo := "EXECUTE" : @Tm    
   quickEventInfo := @appId<1> : "*STPROCEXE**" : atSelf : @Tm    
   quickEventInfo := "@SELF" : @stm : "@EVENT" : @stm : "@PARAM1" : |
                     @stm : "@PARAM2" : @stm : "@PARAM3" : @stm : |
                     "@PARAM4" : @stm : "@PARAM5" : @stm : "@PARAM6" : @tm    
   quickEventInfo := @tm              

   Locate "CLICK" In controlSemantics@< gotIt, 8 > using @svm setting gotClick then
      controlSemantics@< gotIt, 9, gotClick > = quickEventInfo                 
   end

end

So now all we need to do is to take whatever action we want in our commuter program when the CLICK event is triggered. Here's the code from earlier in this blog post.


onClick:

   begin case
      case object = "POPUP.CB_PRINT"
         objxArray = "POPUP.ET_POPUP"
         propArray = "LIST"
 
         objxArray := @Rm : "POPUP.ET_POPUP"
         propArray := @Rm : "SELPOS"
 
         dataArray = get_Property( objxArray, propArray )
 
         list = dataArray[1, @Rm]
         selPos = dataArray[col2()+1, @Rm]
 
         call Msg(@window, "Your cursor is on " : list< selPos<2>, 1>)
   end case

return

Hopefully this will give you ideas of your own to extend the power and flexibility of popups.
By Sprezz | Wednesday, 14 December 2011 16:22 | 0 Comments
A recent Works forum posting posed the question "Is it possible to set the color for individual rows in a popup rather than odd/even row colors?". Received wisdom is that "you cannot change colors on specific rows using COLOR_BY_POS due to the modality of the popup". This set the folks in Sprezz Towers to thinking about how this could actually be accomplished. This blog posting reflects the fruits of the combined thoughts and efforts of both the UK and Philadelphia Sprezz offices.

Take as a simple example a gender popup :-




It might be nice to render this as follows :-



To achieve this we need to be familiar with

  • User Defined Properties
  • The TIMER event
  • The TIMER property
  • The HANDLE property
  • The COLOR_BY_POS send_Message message.
  • The Window structure of a popup

User Defined Properties
The developer can add additional properties to any control within an OI app simply by setting a property name prefixed with an @. So call set_Property(@Window, "@SPREZZ", "FIDDLESTICKS") would set the @SPREZZ property of the current window to the value "FIDDLESTICKS". Any other process could retrieve this property using get_Property.

The TIMER event
The TIMER event is fired by OpenInsight as specified by the TIMER property regardless of whether the current dialog/popup/whatever is currently modal.

The TIMER property
The TIMER property tells the system when it should fire the TIMER event and at what intervals.

The HANDLE property
The HANDLE property provides a convenient shortcut to establish whether or not a control exists. If it exists it will have a handle.

The COLOR_BY_POS message
The COLOR_BY_POS message allows individual rows, columns or cells of an edit table to be set to a specific colour.

The Window structure of a popup
At its simplest a popup consists of an edit table (where the results are shown) and buttons for selection or cancelling. The edit table is always called POPUP.ET_POPUP.

Implementation
It might be easier before actually showing the mechanics of how to achieve the effect required, to describe in simple terms how it is achieved. The key is in the seemingly innocous comment above that  "The TIMER event is fired by OpenInsight as specified by the TIMER property regardless of whether the current dialog/popup/whatever is currently modal".

Within our program that calls the popup we are going to do the following :-

  • Within the OPTIONS event for the prompt set two user defined properties
    • One listing the rows to have their colour set
    • One listing the colours to set the rows to
  • Set a TIMER event for the Window BEFORE calling the popup
  • Call the popup
  • Within the TIMER event, grab the user defined properties, set the colours using COLOR_BY_POS and then disable the TIMER event.
Whilst this may seem like a hack it works remarkably well with simple popups. Complex popups may require more error trapping. 

The OPTIONS event would look like this :-


onOptions:

   objxArray = @Window
   propArray = "@ROWS"
   dataArray = 1 : @Fm : 2

   objxArray := @Rm : @Window
   propArray := @Rm : "@COLOURS"
   dataArray := @Rm : BLUE$ : @Fm : PINK$

   objxArray := @Rm : @Window
   propArray := @Rm : "TIMER"
   dataArray := @Rm : 50

   dataArray = set_Property( objxArray, propArray, dataArray )

   retVal = popup( @Window, "", "GENDER")

   objxArray = object
   propArray = "DEFPROP"
   dataArray = retval

   dataArray = set_Property( objxArray, propArray, dataArray )

return

So we firstly indicate that we would like to change the colours of rows 1 and 2 of the popup. We then explain that we'd like them set to blue and pink (using some previously declared EQUATES). We then tell the TIMER event to kick off 50 milliseconds later and then we call the popup. On a successful return from the popup we accept the value and put it into the control that has just triggered this OPTIONS event.

So the popup will display and within 50 milliseconds the TIMER event will kick in. This has the following code :-


onTimer:

     objxArray = @Window
     propArray = "@ROWS"

     objxArray := @Rm : @Window
     propArray := @Rm : "@COLOURS"

     objxArray := @Rm : "POPUP"
     propArray := @Rm : "HANDLE"

     dataArray = get_Property( objxArray, propArray)

     rows    = dataArray[ 1, @rm]
     colours = dataArray[ Col2() + 1, @Rm]
     handle  = dataArray[ Col2() + 1, @Rm]

     if Len( handle ) then
        ptr1 = 1
        ptr2 = 1
  
        loop
           nextRow = rows[ ptr1, @Fm ]
           ptr1 = col2() + 1
        while len( nextRow )
            nextColour = colours[ ptr2, @Fm ]
            ptr2 = col2() + 1
            call send_Message("POPUP.ET_POPUP", "COLOR_BY_POS", 1, nextRow, nextColour)
        repeat

        objxArray = @Window
        propArray = "TIMER"
        dataArray = ""
 
        dataArray = set_Property( objxArray, propArray, dataArray )
     end

Return

In this code we retrieve the rows to colour and the colours to set them to. We also retrieve the HANDLE  of the popup to ensure that it has been created successfully. If it has we use send_Message to colour the individual rows of the popup before setting the TIMER property to null to prevent it from being called again. In theory we ought to be able to set the TIMER property to 0 but there seems to be a bug in the 9.2.1 implementation which ignores the zero and continues calling the TIMER event.

Thus we are able simply to modify the popup after it has been launched.

In the next article in this series we will show how to add a button to the popup to call a user defined routine to display additional information about the current row in the popup.

Labels: , , ,

By Sprezz | Friday, 25 November 2011 15:40 | 0 Comments
A developer recently asked on the Works forum if it was possible to use the table browser from within a program. Well the simple answer is "Yes". The slightly more complex answer is that since so many of the OI tools are now written in OI itself it is frequently possible to use these tools from within programs. The first thing we need to know is what the table browser window is called. It may come as a surprise to those more familiar with obscure naming conventions to find out that it is called "TABLE_BROWSER". So to call it programmatically we'd simply


atWindow = Start_Window("TABLE_BROWSER", @Window)


Of course launching it might not be enough - we might also want to populate it with the contents of a table. To do this we need to know the control names. To save you the investigative work we've included a table below :-

TABLE_BROWSER The window
TABLE_BROWSER.BTN_CANCEL The cancel button
TABLE_BROWSER.BTN_LOAD The load button
TABLE_BROWSER.BTN_OK The Ok button
TABLE_BROWSER.BTN_OPTIONS The options button
TABLE_BROWSER.DATA_TABLE The results edit table
TABLE_BROWSER.NUM_RECORDS The number of records to display
TABLE_BROWSER.SELECTION_BTN The selection button
TABLE_BROWSER.TABLENAME The table name edit field
TABLE_BROWSER.TEXT_1 The Tablename static
TABLE_BROWSER.TEXT_2 The Number of Records static



So to launch the browser and load the AVERY_LABELS table we could


 Declare Function set_Property, start_Window        

 atWindow = Start_Window("TABLE_BROWSER", @Window)
 objxArray =  atWindow : ".TABLENAME" 
 propArray = "DEFPROP"
 dataArray = "AVERY_LABELS"     

 dataArray = set_Property( objxArray, propArray, dataArray )     

 Call send_Event(atWindow : ".BTN_LOAD", "CLICK")
Pixel
Pixel Footer R1 C1 Pixel
Pixel
Pixel
Pixel