The code framework described in this appendix allows your users to run the application in a bitmapped environment using the mouse both for navigation and menu selection. It solves the problems encountered when migrating character-mode applications to a bitmapped environment. These problems were described in detail in the section "Disabling Default Navigation".
This section gives a brief description of the algorithm. The user can click on anything that is visible. On receiving a mouse click, we will return to the previous (source) location and make use of the key-triggers in your application to navigate to the clicked (destination) location.
The algorithm will first attempt to navigate to the destination block. If it is successful, it will try to navigate to the destination record, followed by the destination item. If it is unsuccessful at any point, it will simply stop wherever the cursor is.
The algorithm will use the KEY-PREVIOUS-* triggers if the destination is the block or record or item that is immediately prior to the source. Otherwise, it will use the KEY-NEXT-* triggers to navigate to the destination. There are a couple of problems:
This algorithm also works in a multiple-form application. We will describe the algorithm in more detail in the next section.
The following sections describe the steps needed to wrap the code framework around your application.
Create the following parameters in your application.
Parameter Name|Data Type|Maximum Length|Default Value|Comments currblk character 50 source block currrec character 3 source record curritem character 50 source item destblk character 50 destination block destrec character 3 destination record destitem character 50 destination item keyop character 1 = t when firing key- triggers = f otherwise maxblk number 5 20 maximum number of blocks navigated to maxitem number 5 30 maximum number of items navigated to
The currblk, currrec and curritem parameters keep track of the source position and the destblk, destrec and destitem parameters track the destination position. maxblk and maxitem are the maximum number of blocks and items navigated to in the algorithm. keyop controls whether
You can customize the algorithm by changing the maxblk and maxitem on the command line when you run your application.
Create the following form level triggers in your application. If you already have these form-level triggers in your application, simply add the code to your triggers.
/* This trigger initializes the source location, a record group to store the row number for each block and turn on Forms' default navigation. */ declare rg recordgroup; blkname char(50); i number; blkcol groupcolumn; rowcol groupcolumn; begin /* initialize current source position */ :parameter.currblk := get_form_property (:system.current_form, first_navigation_block); :parameter.curritem := :parameter.currblk || '.' || get_block_property (:parameter.currblk, first_item); :parameter.currrec := get_block_property (:parameter.currblk, current_record); /* create record group to store current row position of each block. forms has the default functionality of remembering the row number of the blocks it has visited and goes back to that row during navigation. by disabling forms default navigation, we have to keep track of the row number for each visited block. each block is initialized at row 1. */ rg := create_group ('blks'); blkcol := add_group_column (rg, 'blk', char_column, 50); rowcol := add_group_column (rg, 'rownum', char_column, 3); i := 1; blkname := get_form_property (:system.current_form, first_block); while blkname is not null loop add_group_row (rg, end_of_group); set_group_char_cell (blkcol, i, blkname); set_group_char_cell (rowcol, i, '1'); blkname := get_block_property (blkname, nextblock); i := i + 1; end loop; /* set keyop so that any triggers that fire on startup, eg pre-block, when-new-item-instance, etc will be executed. it will be turned off in the form-level when-new-item-instance trigger. */ :parameter.keyop := 't'; end;
find_row is defined as follows. It searches the record group sequentially to find the row number associated with a block. You can replace the algorithm with a more efficient search algorithm if you have many blocks in your form.
FUNCTION find_row (targetblk in char) RETURN number IS i number; blkcol groupcolumn; rg recordgroup; blkname char(50); totalrows number; BEGIN /* loop through the blocks stored in the record group sequentially to find targetblk and return the associated row number */ rg := find_group ('blks'); blkcol := find_column ('blks.blk'); totalrows := get_group_row_count (rg); i := 1; blkname := get_group_char_cell (blkcol, 1); while targetblk <> blkname loop i := i + 1; blkname := get_group_char_cell (blkcol, i); end loop; return i; END;
/* This trigger intercepts all mouse clicks and stores the click position in the DEST* parameters. It also creates a timer so that it can return to the source location. */ declare gotimer timer; i number; begin /* on mouse click, store destination position. */ if :system.mouse_item is not null then :parameter.destitem := :system.mouse_item; :parameter.destblk := :system.current_block; :parameter.destrec := :system.mouse_record; /* since forms has the default functionality of going back to the row that it was in, and when it accepts the mouse :parameter.destitem := :system.mouse_item; :parameter.destblk := :system.current_block; :parameter.destrec := :system.mouse_record; /* since forms has the default functionality of going back to the row that it was in, and when it accepts the mouse click, it remembers the destination row rather than the source row. Therefore, we have to find the source row in the record group and go back there so that forms remembers the original source row. */ if :parameter.destblk <> :parameter.currblk then i := find_row (:parameter.destblk); go_record (to_number (get_group_char_cell ('blks.rownum', i))); end if; /* create a timer so that we can navigate back to the source position. */ gotimer := create_timer('mytimer', 1, no_repeat); end if; end;
/* This trigger returns to the source location and attempts to navigate to the clicked location using the key triggers in your application. */ declare rec_displayed number; i number; blk_nav_ok boolean := true; rec_nav_ok boolean := true; item_nav_ok boolean := true; required_flag char(6); updateable_flag char(6); begin /* first, return to the source position */ /* if the destination item is required, we need to set updatable to false and required to true so that we can return to the source position. */ required_flag := get_item_property(:parameter.destitem, required); updateable_flag := get_item_property(:parameter.destitem, updateable); if required_flag = 'TRUE' then set_item_property(:parameter.destitem, updateable, property_true); set_item_property(:parameter.destitem, required, property_false); end if; /* need go_block, go_record and go_item to return to source position. need go_record for the case when clicking in different item in the same multi-rec block. with go_item only, cursor will just go back to the item in the same record. with go_record in place, need go_block for the case when clicking in item in different block. otherwise cursor will just go back to record in destination block with the go_record. */ /* if the source and destination blocks are different, return to the source block. */ if :parameter.currblk <> :parameter.destblk then go_block (:parameter.currblk); /* if cursor block is different than source block, navigation has failed - do not continue with the algorithm. */ if :system.cursor_block <> :parameter.currblk then blk_nav_ok := false; end if; end if; /* if block navigation succeeds, and source and destination records are different, return to the source record. */ if blk_nav_ok and :parameter.currrec <> :parameter.destrec then go_record (:parameter.currrec); if :system.cursor_record <> :parameter.currrec then /* navigation has failed */ rec_nav_ok := false; end if; end if; /* if block and record navigation succeed, return to the source item. */ if blk_nav_ok and rec_nav_ok then go_item (:parameter.curritem); /* Set the updatable and required properties to their original values */ if required_flag = 'TRUE' then set_item_property(:parameter.destitem, required, property_true); if updateable_flag = 'FALSE' then set_item_property(:parameter.destitem, updateable, property_false); end if; end if; /* Now, use key triggers to attempt to go to the clicked position */ /* if destination block is the previous block, do the previous block key trigger. Otherwise, do the next block key trigger until we reach the destination block or we hit the maxblk limit. */ /* Doing previous block key trigger */ if get_block_property (:parameter.currblk, previousblock) = :parameter.destblk then do_key('previous_block'); /* If current and cursor block are the same after the previous block key trigger, navigation has failed. Otherwise, update current position. */ if :parameter.currblk = :system.cursor_block then blk_nav_ok := false; else :parameter.currblk := :system.cursor_block; :parameter.currrec := :system.cursor_record; :parameter.curritem := :system.cursor_item; end if; else /* Doing next block key trigger */ /* maxblk is needed to prevent an infinite loop because the key triggers may not be able to bring the cursor to the destination position */ i := 1; while blk_nav_ok and i <= :parameter.maxblk and :parameter.currblk <> :parameter.destblk loop :parameter.currblk <> :parameter.destblk loop do_key('next_block'); if :parameter.currblk = :system.cursor_block then blk_nav_ok := false; else :parameter.currblk := :system.cursor_block; :parameter.currrec := :system.cursor_record; :parameter.curritem := :system.cursor_item; i := i + 1; end if; end loop; end if; /* if block navigation succeeds, go to the destination row using the same technique as above. We can rely on the number of records displayed as our limit. */ if blk_nav_ok then i := 1; rec_displayed := get_block_property(:parameter.destblk, records_displayed); while i < rec_displayed and :parameter.currrec <> :parameter.destrec loop if to_number(:parameter.destrec) < to_number(:parameter.currrec) then if :parameter.destitem = :parameter.curritem then /* user has clicked in the same item in a different row */ do_key ('up'); else /* user has clicked in an item different than source in a different row */ do_key ('previous_record'); end if; else if :parameter.destitem = :parameter.curritem then do_key ('down'); else do_key('next_record'); end if; end if; /* If current and cursor record are the same after the key trigger, navigation has failed. Otherwise, update current position. */ if :parameter.currrec = :system.cursor_record then rec_nav_ok := false; else :parameter.currrec := :system.cursor_record; :parameter.curritem := :system.cursor_item; i := i + 1; end if; end loop; /* if record navigation succeeds, go to the destination item using the same technique as above. Here, we rely on maxitem as our limit. */ if rec_nav_ok then if :parameter.currblk||'.'|| get_item_property (:parameter.curritem, previousitem) = :parameter.destitem then do_key('previous_item'); if :parameter.curritem = :system.cursor_item then item_nav_ok := false; else :parameter.curritem := :system.cursor_item; end if; else i := 1; while item_nav_ok and i<:parameter.maxitem and :parameter.curritem <> :parameter.destitem loop do_key('next_item'); if :parameter.curritem = :system.cursor_item then item_nav_ok := false; else :parameter.curritem := :system.cursor_item; i := i + 1; end if; end loop; end if; end if; end if; end if; end;
/* after successful navigation to the destination, update the row number in the record group and current position. at this point, turn Forms' default navigation off. */ declare i number; begin if :parameter.keyop = 't' then :parameter.currrec := :system.cursor_record; i := find_row (:parameter.currblk); set_group_char_cell ('blks.rownum', i, :parameter.currrec); :parameter.currblk := :system.cursor_block; :parameter.curritem := :system.cursor_item; :parameter.keyop := 'f'; end if; end;
In order to turn off Forms' default navigation, we need to wrap some code around your triggers. You should put the following if-statement around your code in the triggers listed in the following figure.
if doit then ... <your code in the triggers listed below> ... end if; PRE-BLOCK POST-BLOCK PRE-RECORD POST-RECORD PRE-TEXT-ITEM POST-TEXT-ITEM WHEN-NEW-ITEM-INSTANCE
Figure A-1 Triggers to be modified doit is a function as shown below. It simply returns whether the value of the parameter keyop is true.
FUNCTION doit RETURN boolean IS BEGIN return (:parameter.keyop = 't'); END;
Since keyop would be false (f) all the time, the code in these triggers will not be executed. We need to turn keyop on (t) using some form-level key-triggers and the ON-FETCH trigger. Create the form-level triggers shown in the figure below with the following line of code. If the application has these triggers, simply add the code at the end of your triggers.
:parameter.keyop := 't'; KEY-NXTBLK KEY-PRVBLK KEY-NXTREC KEY-PRVREC KEY-NEXT-ITEM KEY-PREV-ITEM KEY_DOWN KEY_UP ON-FETCH
Figure A-2 Form-level Triggers The ON-FETCH trigger is required so that the code in the PRE-triggers will be executed when the user executes a query using the menu.
Since the triggers in Figure A-2 are defined at form-level, there are a couple of problems.
Forms will then execute the form-level triggers first, turning on keyop, before executing your triggers. Since keyop is now on, the code in the triggers listed in Figure A-1 will be executed, thereby enabling Forms' default navigation.
However, you have to set the execution style of your WHEN-NEW-ITEM-INSTANCE trigger defined at the block, record or item level to Before since the algorithm should execute your WHEN-NEW-ITEM-INSTANCE trigger code before we turn Forms' default navigation off in the form-level WHEN-NEW-ITEM-INSTANCE trigger.
:parameter.keyop
:= 't';
<do-the-right-thing built-in>
The do-the-right-thing built-in mentioned above is a forms built-in that does the default processing for each trigger. For example, the do-the-right-thing built-in for KEY-NXTBLK is next_block.
The trigger code for KEY_PRVBLK is as follows:
:parameter.keyop := 't';
previous_block;
You can then make all your blocks inherit from this property class. If these triggers are already defined for any block, record or item, your trigger will override those defined in the property class.
Since keyop is always false unless it is a key operation or a mouse click, if your query-related triggers use any restricted built-ins, navigational code will not be executed because we have wrapped the if-statement around all the triggers listed in Figure A-1. In this case, you need to set keyop to true in these triggers. You have to create a procedure to set the keyop parameter to true as follows.
PROCEDURE setit IS BEGIN :parameter.keyop := 't'; END;
There are only a few places that you need this. You have to call setit at the beginning of the ON-CLEAR-DETAILS and ON-POPULATE-DETAILS triggers and any forms-generated CLEAR-***-DETAILS procedures if they use restricted built-ins.