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

#76427 by Mark F. Madura
Fri May 24, 2019 4:22 pm
Converting the Main procedure in a DCAL for DOS program to a DCAL for Delphi library

The Main procedure in most DCAL for DOS macros is the first procedure to be called and takes care of initializing global variables, setting up the main menu, establishing the initial method of input expected from the user, and what functions will be called based on that input.

Capturing the user's input and calling functions based on that input is typically handled within nested REPEAT loops. This flow of control is not compatible with DCAL for Delphi's re-entrant state machine structure. In particular, setting up the menus and capturing the user's input must occur in the Action Section separate from what functions are called based on that input, which must occur in the Again Section where the State Machine waits for user input.

If multiple input steps are necessary, such as asking for multiple points, or for a distance followed by an angle, nested repeat loops cannot be used. Case statements, defined within specific states, must be used to structure each input step separately. As the name implies, the State Machine uses various states to control the flow of your program. In DCAL for Delphi, these states are AlSize, AFirst, AAgain, and ALast.

AFirst: The Initialization Section
The tasks you include here will only be performed once. So, this is where you'll initialize variables and display the macro's description on the error line. Most importantly, this is where you'll set the initial state of your macro, typically l.state := 1, the first case in the Action Section.

AlSize: The Action Section
This is where memory for your local variable record will be allocated on the memory stack and DataCAD's dispatcher will be invoked. You'll define this 'L' record (a.k.a. Local Variable Record) to carry the state, other variables, and user-input argument results. Here is the L record for the Arrow macro.

Code: Select alltype
  ArrowL = record
    state: asint;
    pnt: array [1 .. 2] of point;
    numpnt: asint;
    arrwidth: afloat; { the width of the arrow }
    case byte of
      0: (getp: getpointarg);
      1: (getd: getdisarg);
  end;


This record includes an integer variable for the state. Every L record must have this variable. The other variables are specific to the arrow macro such as the end points and the width of the arrow. This macro includes two input arguments. getp for capturing user points (or menu keys), and getd for getting a distance value (or menu keys). At a minimum, a case for gete: getescarg must be declared to capture menu keys.

You can setup your main menu in case 1 of the Action Section. For example:

Code: Select allcase l.state of
  1: begin
      wrtlvl('Arrow'); { Set Menu Title }
      lblsinit; { Initialize function key labels }
      lblset(1, 'Width'); { Set function key label }
      lblset(20, 'Exit'); { Set function key label }
      lblson; { Turn on the function keys }


Then you'll prompt the user for what they should do when they first run your macro. So, just like DOS, lblson is usually followed by a prompt like wrtmsg('Enter first point of arrow.'). The Arrow macro requires two points to be entered by the user one after the other. In DOS, the first point is captured in the first REPEAT loop and the second point is captured in a nested REPEAT loop. In Delphi, the first point will be captured in Action Case 1. The second point could be captured in Action Case 2, but we've created a variable numpnt in the L record to keep track of which point is to be entered. This way, we can return to Action Case 1 and capture the second point after the first point has be defined.

In DOS, the fist point is captured using the getpoint function. For example:

Code: Select allresult := getpoint (pnt1, key);


In Delphi, the fist point is also captured using the getpoint function, but the result is stored in the getpoint argument l.getp. For example:

Code: Select allgetpoint (l.getp, retval);


Once the point is captured (or a menu key is pressed) in Action 1, the Main DCAL function returns to AAgain 1. This is where you'll handle the results of the user's input.

Code: Select allAAgain: The Re-entrant Section – Return to menus and wait for input[/b]
In AAgain 1, we look at the results of the user's input in Action 1. For example:
case l.state of
  1: begin
      if l.getp.Result = res_escape then begin { User clicked a menu }
        case l.getp.key of
          f1: l.state := 2; { Width – Go to Action 2 }
          s0: l.state := 0; { Exit – Go to Action 0 XDone }
        end;
      end
      else if l.getp.Result = res_normal then begin { User clicked a point }
        l.pnt[l.numpnt] := l.getp.curs;
        if l.numpnt = 1 then begin
          l.numpnt := 2;
        end
        else begin { Done getting points, add arrow }
          addarrow(l.pnt[1], l.pnt[2], l.arrwidth, l.isfill, l.fillcolor);
          l.numpnt := 1; { Finished adding arrow, reset numpnt to 1 }
        end;
      end;
    end;
  2: l.state := 1; { Finished getting arrow width, go to main menu }
  else l.state := 0; { Exit - Go to Action 0 XDone }
end;


Then, based on the user's action, we'll either go to a different Action State like Arrow Width, or if a point has been captured, we'll store the point(s) in the local record, add the arrow, then return to Action 1.

ALast: The Interrupt Section
When the user is at the main menu, they can select F0: Exit or right-click to exit the macro. In this case, l.state is set to 0 and the value returned to the Main function is XDone which tells DataCAD to close the macro. If, however, the user presses a hot key to 'jump out' of your macro, DataCAD will set the state to ALast where you'll have an opportunity to clean up any temporary data your program may have created.

I hope this helps clear up the relationship between a DCAL for DOS macro and a DCAL for Delphi macro so you might be encouraged to convert and update some of your existing macros. There are a number of benefits including much greater speed of execution, support for double-precision floating point math, and taking advantage of anything the Delphi and Windows development environments have to offer.
#76428 by dhs
Fri May 24, 2019 6:17 pm
Thanks Mark,

I thought you had pretty much lost interest in DCAL, so it's good to see you pro-actively posting to the DDN forum (2 posts within just a few hours!). This post is useful and I hope it encourages others to look more closely at DCAL for Delphi.

As you note, DCAL for Delphi has many advantages over Classic DCAL. You miss what I consider one of the biggest advantages ... the ability to use the Delphi debugger (https://www.dhsoftware.com.au/delphidebug.pdf). When I wrote the Space Planner macro I spent many many hours adding debugging code to try to work out what was causing problems ... I am now writing a successor macro in Delphi and can simply step through the logic and examine variables at each step if required.

But there are also disadvantages in using DCAL for Delphi. The main one is the lack of any equivalent to the Classic GetMode procedure ... anybody proposing to convert an existing macro that uses GetMode will have a much bigger task. I also note some other disadvantages on my page at https://www.dhsoftware.com.au/dcalcompare.htm (please let me know if you disagree with any of the statements on that page). Mostly I would recommend using DCAL for Delphi, but any potential user needs to be aware of the disadvantages.

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