The DataCAD Developer Network (DDN) is an online resource for information and support for DCAL® (DataCAD Applications Language) developers as well as anyone interested in creating fonts, toolbars, hatch patterns, or linetypes for use in DataCAD.

Moderator: pjdixit

#72320 by Jsosnowski
Sun Sep 17, 2017 3:38 pm
PART FOUR - A DCAL FOR DELPHI PRIMER - _DoMenu FUNCTIONS

This posting covers the 'Big Kahoona' for operating D4D macros. Structuring and managing the menu loop function is both the most important and complex coding required in creating a macro. In this post we will examine the structure of a <name>_doMenu function in detail to understand how the data from these pointers direct the focus of the macro during its execution. This is a big subject and so the full discussion in this post is divided into several sections included as separate replies.

Overview

As discussed in previous postings, all Datacad macros operate as a loop, but PART TWO described that D4D macros can end up calling D4D in different drawing files, using different data and at different points in execution. That is why the p_LR & p_DR arguments are used to pass all relevant information in each loop iteration. The opportunity for a user to change drawings and require different argument values to be passed to the macro occurs every time keyboard and mouse control passes back to Datacad. The DCAL & D4D programming environment provides many procedures and function for working within a Datacad drawing. All available methods are listed in the 'uinterfaces.pas' file. For our purposes here they can be divided into two categories, operational and interfaces. Operational methods perform calculations or interrogate the drawing file. Interface methods require user input and can be identified in the 'uinterfaces.pas' file by their use of an argument of the type 'wanttype'. Any function or procedure that contains a 'wanttype' argument can potentially lead to a change in drawing file requiring different argument values passed to the macro. There are quite a few of these interface methods including the most common ones such as getesc() and getPoint(). To work correctly your macro should end execution at the point of any of these user input interface methods.

In the PART ONE posting we discussed the original DCAL doMenu function as the following:
Code: Select allprocedure Dcal_MenuLoop;
var
  //declare any variables
begin
  //initialize variables
  repeat    //menu loop
    //display section  - display the menu
    //input section  - get the user's selection
    //execute section  - act upon selection made
  until done;
  //finalize loose ends before exit
end;

The 'Repeat' loop continues until the user selects an menu option to exit the macro (usually a 'S0' function key). The difference with D4D macros is that the macro releases control during the current loop when a Datacad user interface function call is made. All data and status information is passed back to Datacad by arguments Act, p_LRr, & p_DR to be, in turn, handed back at the next call to the macro in the current drawing. At this point the macro does not retain any previous iteration information such as whether it must perform display and input steps or execute an action next. In order to correctly pick up where we left off we must add code to route focus in the <name>_doMenu to the correct location. To start this process the D4D <name>_doMenu function looks like:

Code: Select allfunction <name>_doMenu (act : action; P_LR, P_DR : Pointer) : wantType;
var //declare required and optional variables

begin
  //START SECTION
        //SIZING STEP  -  Datacad inquiry for memory allocation
        //INITIALIZE STEP  -  startup activities
        //ROUTER STEP  -  Determine route based upon input
        //SPECIAL EXIT STEP - interrupted macro activities
  //EXECUTE SECTION     
        //DISPLAY STEP  - display the menu
        //INPUT STEP  -  Get user input
        //EXECUTE STEP  -  Act upon user input
 
end;

First notice that although DCAL and D4D structures initially appear to be different, both menu structures inlcude the same steps. In this discussions, for simplicity, the D4D menu is divided into a STARTUP and EXECUTE SECTION. In practice, this division is not a strict requirement, and is not always strictly followed in sample macros provided by Datacad, but is described as such here to assist users in understanding what is happening in the menu flow. Each step is discussed in more detail below with code snippets for each section. Full template versions of the complete menu procedure are provided in the PART FIVE postings. Additional reply posts in this Post will discuss how to code the menu in parts including:

1. Declaring Variables
2. Defining the START SECTION
3. Defining the EXECUTE SECTION
4. Understanding DataRecord Flow
Last edited by Jsosnowski on Sun Sep 17, 2017 3:50 pm, edited 1 time in total.
#72321 by Jsosnowski
Sun Sep 17, 2017 3:40 pm
Declaring Variables

As discussed in PART THREE the essential data necessary for a D4D macro is contained the p_LR andp_DR arguments. Two local variables are defined in each <name>_doMenu function along with a 'wanttype' variable used to assign a 'result' for the menu function.

Code: Select allunit <name>;
...
interface
...
Var
  theData : t<name>DataRecord; {6a}
...
implementation
...
function <Name>_doMenu(act : action; p_LR, p_DR : Pointer) : wantType;
var
   retval : wantType;  {1}
   lr : p<name>LoopRecord;  {2}
   dr : p<name>DataRecord; {3}
// Add any local variables  {4}
begin
    //link function arguments to local pointers
   lr := p<name>LoopRecord(p_LR); ; {5}
   
   //use one of the options below. see discussion in separate reply in this post.
// dr := addr(theData);  //initial declaration  {6b}
// dr := p<name>DataRecord(p_DR);   //used by any subsequent doMenu functions linking to an established pointer {6c}
   
   Case act of
   alsize: ...
end; //<name_doMenu

end.

Notes:
{1} - Declare a local variable, retval, to hold the return value for the function. The variable 'retval' is of type 'wantType' which is defined by D4D as an 'asint', currently identified as an integer. (The renamed type asint is used by Datacad so that if the type of some variables such as 'integer' is changed by Delphi in the future only one line of code need be rewritten). The function's result is assigned the 'retval' value to pass it along to Datacad's dispatcher from the Main function. This value will, in turn, be assigned to the 'dcalstate' argument value by the dispatcher for use in the next iteration.
{2} - This is the pointer to the current t<name>LoopRecord variable that will be linked with the memory location passed into the function by the <name>_doMenu function argument P_LR. The actual variable and its memory allocation is declared and managed by Datacad.
{3} - This is the pointer to the current variable that will hold your macro data linked with the t<name>DataRecord. It is passed using the argument p_DR.
{4} - Add any other local variables that are used that do not need to be preserved between menu loops.
{5} - Link the local function pointer variable lr to the pointer p_LR. Note that the assignment to lr is typecast as p<name>LoopRecord to enable your code to reference the fields of the record. As discussed in PART THREE Datacad queries your macro in the act = alsize case in the STARTUP SECTION, reserves memory and returns a pointer to its location in the memory P_LR argument.
{6a,b,c} - Define and link the t<name>_DataRecord variable and pointer. As discussed above, memory for the actual data associated with the p_LR pointer is reserved by Datacad. Memory for the data referred to by p_DR must be declared and assigned by your macro. The memory is for the data is reserved in step {6a} by declaring a variable instance of type <name>DataRecord at the unit file level. This variable should persist for life of the macro. To do so your macro will use either {6b} or {6c} declarations. The correct use for each line is discussed in the reply regarding 'DatRecord Life Cycle' in this Posting.
#72322 by Jsosnowski
Sun Sep 17, 2017 3:44 pm
START SECTION

The START SECTION includes the setup of actions required to operate the menu using the Act argument provided by Datacad's dispatcher.

Code: Select allfunction <name>_doMenu (act : action; P_LR, P_DR : Pointer) : wantType;
var //declare any variables
begin
                                                   //START SECTION:
  case act of   
                                                      //SIZING STEP:   
    alsize: begin
      // inform Datacad of the size of the tLocalL record
    end;
                                                      //INITIALIZE STEP:   
    aFirst:  begin
      // set up variables and other conditions as needed
    end;
                                                      //ROUTER STEP:     
    aagain: begin
      //direct the focus route through the macro based upon the previous iteration
    end;
                                                      //SPECIAL EXIT STEP:   
   aLast: begin
      // perform any macro housekeeping activities when the macro is terminated by a Datacad shortcut such as 'Ctrl M' for the 'Move' menu.
    end; 
  ...
 


alsize

As discussed in PART TWO, Datacad starts each macro using the function named 'Main'. Main searches a 'dcalstate of' structure seeking the first state 'XDcalStateBegin' which is often renamed 'XMain' in Datacad sample macros, and loads the associated menu function attached to the case instance. In the first iteration of the menu loop it asks your macro the size of memory to reserve to hold the t<name>LoopRecord by assigning the value 'alsize' to the dcalstate argument, so that it can work with it and pass status information back to the macro in subsequent loops. It does so using the 'setlocalsize()' procedure declared in 'uinterfaces.pas'.

Code: Select allfunction <name>_doMenu (act : action; P_LR, P_DR : Pointer) : wantType;
...

                                                   //START SECTION:
  case act of
  ...
                                                      //SIZING STEP:     
    alSize:
     begin
       setlocalsize(sizeof(lr^);
      end;
  ...


alfirst

In the next iteration of the loop Datacad assigns the value 'afirst' to the dcalstate argument. The macro should insert any code here necessary to initialize any persistent variables for use during the macro lifetime. These variable values should be contained as fields in the t<name>DataRecord structure you declare for use in the macro. In the 'Declaring Variables' section we declared this variable as 'theData' in the interface section of the unit file.

Code: Select allfunction <name>_doMenu (act : action; P_LR, P_DR : Pointer) : wantType;
...
  case act of
  ...
                                                      //INITIALIZE STEP:     
    afirst:
     begin
       //initialize data code here
        lr.state := 1;
      end;
  ...

Note that the value of lr.state is always set to 1 in the 'afirst' iteration. The value for lr.state will direct the flow used in all future iterations of the menu.

aagain

After the first two iterations of the menu function, the Datacad dispatcher will set the dcalstate value to 'aagain', the ROUTER STEP for the menu. In the 'afirst' iteration we set lr.state := 1. By changing the value of lr.state we control the flow through the menu router in each iteration. The value of Lr.state' will be changed in accordance to user input obtained in any previous loop. The initial value is always set to '1' which will display the menu and ask for user input later in 'EXECUTE SECTION' of the function.

Code: Select allfunction <name>_doMenu (act : action; P_LR, P_DR : Pointer) : wantType;
...
                                                   //START SECTION:
  case act of
  ...
                                                        //ROUTER STEP
  aagain:
    begin   
      case lr.state of
     1 : {1}
     begin
       case lr.gete.key of  {2}
          f1 : lr.state := 2;
        f2 : lr.state := 3;
        s10: lr.state := 0;  {3}
        end;    
      end;
      2, 3:   {4}
      begin
      lr.state := 1;
     end;
      else {5}
        lr.state := 0;
   end;   
  ...


{1} Since the 'afirst' case above set the value of lr.state to 1, the next loop through the menu will execute the code in 'case lr.state of 1:'.
{2} As we will show in the EXECUTE SECTION below, this menu sample is getting user input using the getesc() procedure defined in 'uinterfaces.pas'. That procedure will assign a function key value to the record field lr.gete.key. The first time the focus passes through the value will not include a function key value and the case will not change the value of lr.state. This insures that the default EXECUTE STEP option is followed, displaying menus and asking for input. If the user selects a function key then, this 'Case' code will change the value of lr.state resulting in other actions in EXECUTE STEP. A different lr.state value is assigned for each function key option that requires an action.
{3} If the user selects the function key S0 then the value of lr.state is set to 0; which will instruct Datacad's dispatcher to end the macro operation.
{4} In most cases, once an EXECUTE STEP has been completed, the menu returns to normal operation asking for new user input in the next iteration. Therefore, if the lr.state has been set to execute code associated with a function key in the previous loop, this case changes the lr.state back to 1 for continued starting operation of the menu. Note that a case number must be included for each function key option included in [2} above.
{5} Always include a final lr.state := 0 to catch any problem that may arise in the operation of the macro.

alast

Use the 'alast' case performs any clean up activities needed when the user selects a keyboard shortcut/hotkey. Under normal operation the users will inform you that they want to exit the macro by selecting the S0 function key. Your code can intercept that key lr.state := 0 by changing the lr.state value to an execute step value to include code for saving settings etc.. Since the macro will have no such warning if the user selects a shortcut/hotkey the dispatcher provides 'case act of alast:' to close the macro properly.

Code: Select allfunction <name>_doMenu (act : action; P_LR, P_DR : Pointer) : wantType;
...
                                                 //START SECTION:
 case act of
   ...
                                                     //SPECIAL EXIT STEP   
  aLast:
    begin
      //emergency exit
    end;
   ...


Check out the EXECUTE SECTION reply in this PART FOUR posting for more on coding the rest of a <name>_doMenu function.
Last edited by Jsosnowski on Sun Sep 17, 2017 3:54 pm, edited 4 times in total.
#72323 by Jsosnowski
Sun Sep 17, 2017 3:46 pm
EXECUTE SECTION

The previous replay in this PART FOUR Posting covered considerations writing code for the START SECTION of a <name>_doMenu function. This posting reply addresses considerations for the EXECUTE SECTION. By this point, the function has initialized variables and established the route that macro focus should take in this section using the value of lr.state to direct the action.

Code: Select allfunction <name>_doMenu (act : action; P_LR, P_DR : Pointer) : wantType;
 ...
                                                   //EXECUTE SECTION:
 if act <> alSize then begin  {1}
    case lr.state of
                                                       //DISPLAY STEP   
      1:
      begin               {2}
       wrtlvl ('LvlName');
        lblsinit;
        lblset (1, 'Option1');
        lblset (2, 'Menu2');
        lblset (20, 'Exit');
        lblson;
                                                       //INPUT STEP
       getesc(lr.gete, retval); {3}
           //end loop iteration here
     end;
                                                       //EXECUTE STEP
      2:
      begin               {3}
       do_F1Option;
        callnone(retval);
     end;
      3:
      begin               {4}
      call_Menu2 (dr, retval);
      end;
      else  begin         {5}
        retval := XDone;     
      end; 
    end; //case
end; //doMenu


{1} The balance of the code in the menu function applies to every dcalstate except alsize. The values for lr.state implemented in the router section of the menu function are implemented here. Create a separate case lr.state value for each desired action to be implemented in the 'case act of aagain:' in the ROUTER STEP.
{2} 'Case lr.state := 1' displays the menu. The commands and setup are the same as in DCAL macros.
{3} 'Case lr.state := 2'. This is an example code for executing non-interface commands or functions. Remember that interface functions will contain an argument of type 'wanttype' and are passed back to Datacad's dispatcher as a result value for the function. Since non-interface actions do not return a value, it is necessary to notify the dispatcher directly that it mst be activated. This is accomplished by adding the function callnone(retval);, This command insures that Datacad will intiate the next loop when this menu function is completed.
{4} 'Case lr.state := 3'. This is an example of calling another menu function. A sample of function call_Menu2 follows below.
{5} This 'else' condition is used for all other conditions serving as a catch all for any exceptions. The value 'Xdone' is assigned in 'uconstants.pas' as 0, ending the macro execution.

callnone(retval);

It is necessary to notifyDatacad's dispatcher that you are calling another menu. This is accomplished by creating a procedure to convey the correct information to Datacad.

Code: Select allfunction call_<name>_doMenu (data : p<name>datarecord; var iwant : wantType);  {1}
begin
  setargs (data);          {2}
  iwant := X<name>Menu;    {3}
end;

{1} Create a new call function for each new menu included in the macro. The 'Data' argument is a pointer to the type of datarecord your macro is passing on. Note that the value for the pointer must have been set in the current menu. Iwant is a variable argument that sets the retval in the current menu which is calling this function. That retval will be passed back to the Datacad dispatcher when the current menu function ends.
{2} The procedure 'setargs()' is used to inform the dispatcher of the value for the datapointer so it can return it in the call for the menu requested in the next loop.
{3} The 'iwant' argument value is set to a constant such as x<name>Menu. How to define and use these values is discussed in the PART TWO posting describing the 'Main' function operation.
#72324 by Jsosnowski
Sun Sep 17, 2017 3:48 pm
Understanding DataRecord Flow.

In the other sections of this PART FOUR post, we have seen the introduction and implementation of the persistent data variables in a macro. Although we have seen it in code snippets, it would be helpful to understand it over the lifetime of the macro.

All variable values that persist over the lifetime of the macro must be placed in a single record structure with a declared type and associated pointer.

Code: Select allUnit Example;
...
type
  t<name>DataRecord : record
    a,b,c : shortstring;
    d,e,f : integer;
  end;
  p<name>dataRecord : ^t<name>DataRecord

var
  theData
...

After these declarations the data must be attached to the menu operation using the p_DR arguments in menu calls. The initial link is made in the first or 'Main' menu function.

Code: Select all...
implementation
...
function <Name>_doMenu(act : action; p_LR, p_DR : Pointer) : wantType;
var
   retval : wantType; 
   lr : p<name>LoopRecord; 
   dr : p<name>DataRecord;
// Add any local variables 
begin
   lr := p<name>LoopRecord(p_LR); ;   
   dr := addr(theData);  //initial declaration  {1}
 
   Case act of
   alsize: ...
end; //<name_doMenu
...

{1} The local variable is assigned the value of the pointer for 'theData' record which was defined as a unit level variable.

Every time the menu loop returns, this pointer is refreshed automatically. As discussed in the EXECUTE STEP reply in this PART FOUR posting, the new menu is called using the procedure call_<name>_doMenu procedure which now passes the pointer 'dr' to Datacad using setargs(). When the new menu is called the P_DRpointer will be set to the correct memery location address for 'theData'. The new menu function now links to the same location using a slightly different assignment.

Code: Select all...
implementation
...
function <Name>_doMenu(act : action; p_LR, p_DR : Pointer) : wantType;
var
   retval : wantType; 
   lr : p<name>LoopRecord; 
   dr : p<name>DataRecord;
// Add any local variables 
begin
   lr := p<name>LoopRecord(p_LR); ;   
   dr := p<name>DataRecord(p_DR);  //connect to a p_DR argument value
 
   Case act of
   alsize: ...
end; //<name_doMenu
...

Note that both dr assignment code lines are included in the templates and examples above. Which one to use depends upon whether this is the initial menu assigning the value or a seondary menu that is linking to the pointer.

Who is online

Users browsing this forum: No registered users and 2 guests

About DataCAD Forum

The DataCAD Forum is a FREE online community we provide to enhance your experience with DataCAD.

We hope you'll visit often to get answers, share ideas, and interact with other DataCAD users around the world.

DataCAD

Software for AEC Professionals Since 1984