In this post, you learn how to create custom dialog boxes for LISP routines with DCL (Dialog Control Language). In the last post we created a basic dialog box, in this post, we'll add the bells and whistles.

For a full overview of DCL commands for BricsCAD, you can view the official, BricsCAD DCL Developer Guide.

QUICK SUMMARY OF DCL METACHARACTERS

DCL METACHARACTER MEANING
// (slash-slash) Indicates a comment line
/* (slash-asterisk) Starts comment section
*/ (asterisk-slash) Ends comment section
: (colon) Starts a tile definition. Predefined tiles, like spacer, do not use the colon
{ (brace) Starts dialog and tile attributes
(space) Separates symbols
= (equals) Defines attribute values
“” (straight quotation) Encloses text attributes
; (semi-colon) Ends attribute definition. Every attribute must end with a semi-colon
} (brace) Ends tile and dialog attributes

Start Dialog Box Definition

The content of every .dcl file begins with a name attribute. This is the name by which the associated LISP routine calls the dialog code. The name function looks like this:

name: dialog {

Like LISP, an open brace needs a closing brace to signal the end of a dialog box definition:

}

Between the two braces you write all the code the tells BricsCAD to look of the dialog box.

For this tutorial, name the dialog box "lastInput," as follows:

lastInput: dialog {

}

DCL names are case-sensitive, so "lastInput" is not the same as "LastINPUT" or "lastinput."

Dialog Box Title

The text for the dialog box's title bar is specified by the label property, as follows:

name: dialog {

label = "Dialog box title";

}

Label this dialog box "Last Input" like this:

lastInput: dialog {

label = "Last Input";

}

The title text needs to be surrounded by quotation marks ( " ). The label property must be terminated with a semicolon ( ; ). And it's helpful to indent the code to make it readable.

OK Button

Every dialog box needs an exit button, at least an OK. (Windows places a default X button in the
upper-right corner of every dialog box, which also works to exit dialog boxes made with DCL!)

Buttons are defined with the button property, followed by the properties of the button enclosed
in braces:

: button {

}

Because dialog boxes can have multiple buttons, every button must be identified by a property
called the "key." The key is how LISP gives instructions to buttons. Use the key attribute to identify this OK button as "okButton," as follows:

key = "okButton";

The button needs to display a label for users to read. This is an OK button, so label it " OK " with the label attribute, as follows:

label = " OK ";

Let's put all of these together. The code added for the OK button is shown here in color, with the key, label, and is_default attributes. (See below for info about the default attribute.) We have a button identified as "okButton," sporting the label "OK," and is set at the default tile.

lastInput: dialog {

label = "Last Input";

: button {

key = "okButton";

label = " OK ";

is_default = true;

}

}


TIP The DCL code for the OK button is like a subroutine. The same code can be reused any time a dialog box needs an OK button, which is pretty much all the time. Later, you will see how to create subroutines with DCL code.


The Default Tile

To make life easier for users, one tile of a dialog box is always made the default tile. Users need only press Enter to activate the default tile. Dialog boxes highlight the default tile in some way, such as with a dashed or colored outline. Users can press Tab to move the default focus (currently highlighted tile) to other areas of the dialog box.

A tile is made the default with the is_default attribute, as follows:

is_default = true;


LISP CODE TO LOAD AND RUN DIALOG BOXES

The following LISP code is what you use to load, run, and exit the lastInput.dcl dialog box definition file:

(defun C:xx ()

(setq dlg-id (load_dialog "c:\lastInput"))

(new_dialog "lastInput" dlg-id)

(action_tile "accept" "(done_dialog)")

(start_dialog)

(unload_dialog dlg-id)

)

To see what the LISP code means, let's take it apart. The function is defined as "xx" with LISP's defun function. Programming = debugging, so I like to use an easy-to-enter name for the LISP routine, like "xx."

(defun C:xx ()

The lastInput.dcl file is loaded with the load_dialog function. There is no need to specify the ".dcl" extension, because this is the sole purpose of this function: to load DCL files.

  • In Windows, include the name of the drive, C:\. Recall that LISP requires you to use \ instead of \ for separating folder names.

    (setq dlg-id (load_dialog "c:\lastInput"))

  • In Linux, leave out the name of the drive:

    (setq dlg-id (load_dialog "lastInput"))

DCL files can contain more than one dialog box definition, and so the next step is to use the new_dialog function to tell BricsCAD which one you want to access. In this case, there is just the one, "lastInput."

(new_dialog "lastInput" dlg-id)

The dialog box contains a button named "okButton," and its purpose is defined by LISP --- not DCL! Here you use the action_tile function to assign an action to the "okButton" tile. The button's purpose in life is to execute the done_dialog function that exits the dialog box. In short, click OK to exit the dialog box. You can read this as follows: "the action for the tile named okButton is ...".

(action_tile "okButton" "(done_dialog)")

After all these preliminaries, the big moment arrives. The start_dialog function launches the dialog box and waits for you to click its button.

(start_dialog)

As neat programmers, we unload the dialog box from memory with the unload_dialog function.

(unload_dialog dlg-id)

And a final parenthesis ends the xx function.

)


Testing DCL Code

You have enough DCL code to test it now, which lets you see how the dialog box is developing. To test the code, take these steps:

  1. Open Notepad, Text Edit, or another ASCII text editor.

  2. Enter the DCL code we developed earlier:

    Making Custom Dialog Boxes- 1

    Entering DCL code into a text editor

    Important: Ensure that this DCL file and the LSP file use straight quotation marks that look like this: ". If they contain curly quotation marks ( " or " ), the routines will fail. LISP will complain, "error: bad argument type; expected " while DCL will put up a dialog box complaining, syntax error: unexpected "".

  3. Save the file as lastinput.dcl. So that the LISP xx.lsp routine can find it easily, save the file in a top level folder:

    • In Windows, save the DCL file to the C:\ drive.
    • In Linux and Mac, save the DCL file to your home folder. For example, I log in to Linux with the user name of "ralphg," so I saved the file in the ralphg folder.
  4. Now, open a new file, and then enter the LISP code described in the boxed text on the following page: "LISP Code to Load and Run Dialog Boxes."

    Making Custom Dialog Boxes- 2

    Entering LISP code into a text editor

  5. Save the file as xx.lsp, also in the same folder as the DCL file

  6. Switch to BricsCAD, and then open the xx.lsp file in BricsCAD:

    • In Windows, use Explorer to drag the xx.lsp file into the BricsCAD drawing window. (Dragging the file is a lot faster than entering the AppLoad command or using the LISP load function!)

    • In Linux, you have to use the load function, because files cannot be dragged into the Linux version of BricsCAD. Enter the following at the ':' prompt:

      : (load "xx")

  7. Type xx to run the routine, which then loads the dialog box:

    : xx

    Notice that the dialog box appears! Well, it should, if you haven't made any coding errors.

    Making Custom Dialog Boxes- 3

    Left: Dialog box in Windows 7.
    Right: Dialog box in Linux Mint

  8. Click OK to exit the dialog box.
    Here is a map of how the DCL code created the dialog box:

    Making Custom Dialog Boxes- 4

    The dialog box created by the DCL code

DISPLAYING DATA FROM SYSTEM VARIABLES

The basic structure of the dialog box is in place: the label and the OK button. Now it is time to add the data we want to be displayed by the system variables.

The data from the sysvars will look like this in the dialog box:

Last angle: 45
Last point: 1,2,3
Last prompt: Line

I show the static text in color. It never changes. This text acts like a prompt to tell users what the numbers mean.

The black text is variable; its display changes and depends on the value of the associated sysvar.

The Text tile is the one that displays text in dialog boxes and its code will look like this:

: text {

label = "Last angle: ";

key = "lastAngle";

}

Are you able to recognize the attributes of this text tile?

Begin the text with this tile:

text {

Next, the label attribute provides the prompt, 'Last angle: '.

label = "Last angle: ";

Next, the label attribute provides the prompt, 'Last angle: '.

label = "Last angle: ";

The key attribute identifies the text tile as "lastAngle."

key = "lastAngle";

Finally, the text tile is closed with the brace.

}


TIP Text tiles can have the following attributes:

  • alignment
  • fixed_height
  • fixed_width
  • height
  • is_bold
  • key
  • label
  • value
  • width

Add the highlighted code to the DCL file...

Making Custom Dialog Boxes- 5-585x316

Adding DCL code in the text editor

...and then run the xx.lsp routine again. Notice that the dialog box now displays the 'Last angle:' text:

Making Custom Dialog Boxes- 6

Dialog box that results from the DCL added code

The next step is to display the value stored by the LastAngle system variable. Add a second text tile:

: text {

value = "";

key = "lastAngleData";

}

The value of this tile is initially blank because it has no label and no value. To complete the text tile, we need to use a LISP function that extracts the value from the LastAngle system variable and then shoves it into the dialog box.

The link between the LISP code and the DCL file is with the key, which is named here "lastAngleData." (I'll show you the LISP code a bit later on.) Now the DCL file looks like this, with the new code shown in color. You can copy this code and paste it into the text editor.

lastInput: dialog {

label = "Last Input";

: text {

label = "Last angle: ";

key = "lastAngle";

}

: text {

value = "";

key = "lastAngleData";

}

: button {

key = "okButton";

label = "OK";

is_default = true;

}

}

(If you were to run this DCL code now, the dialog box would look no different. It still needs LISP to
tell it the value of the last angle. This is coming up next.)

ADDING THE COMPLIMENTARY LISP CODE

Writing DCL code is always only half the job. The other half is to write the complementary code in LISP. Extracting the value from LastAngle take these two steps:

Step 1: Use the getvar function to access the value of sysvar LastAngle, and then store the gotten value in variable lang (short for "last angle") with the setq function, as follows:

(setq lang (getvar "LastAngle"))

Step 2: Use the set_tile function to set the value of lang to the "lastAngleData" tile:

(set_tile "lastAngleData" (rtos lang 2 2))


TIP Tiles work only with text, no numbers. However, the value of LastAngle is a number, so you have to convert it to text. This is done with the rtos function:

(rtos lang 2 2))

Here, I am converting the real number to a string (a.k.a. text) using mode 2 (decimal) and precision 2 (two decimal places).


With the new lines of code shown in color, the LSP file now looks like this:

(defun C:xx ()

(setq dlg-id (load_dialog "c:\lastInput"))

(new_dialog "lastInput" dlg-id)

(setq lang (getvar "lastangle"))

(set_tile "lastAngleData" (rtos lang 2 2))

(action_tile "okButton" "(done_dialog)")

(start_dialog)

(unload_dialog dlg-id)

)

Save the .dcl and .lsp files, and then reload and run xx.lsp in BricsCAD.

The dialog box now looks like this:

Making Custom Dialog Boxes- 7

Dialog box reporting the angle value

CLUSTERING TEXT

Hmmm... the two pieces of text are stacked on top of one another and that is a problem. They should be horizontal. The text is stacked vertically because DCL places tiles in columns by default.

The solution is to force the two text tiles to appear next to each other with the Row tile:

: row {

: text {

label = "Last angle: ";

key = "lastAngle";

}

: text {

value = "";

key = "lastAngleData";

}

}

Modify the DCL file by adding the row tile, and then rerun the LISP file. The result should look better, like this:

Making Custom Dialog Boxes- 8

Angle text formatted into a single line

Now that the last-angle text looks proper, you can copy and paste its code for use by the other two lines and then make suitable modifications. The changes you need to make are shown below in color:

: row {

: text {

label = "Last point: ";

key = "lastPoint";

}

: text {

value = "";

key = "lastPointData";

}

}

And for final prompt:

: row {

: text {

label = "Last prompt: ";

key = "lastPrompt";

}

: text {

value = "";

key = "lastPromptData";

}

}

Running xx.lsp gives the dialog box all three prompts, but data is missing from the two new ones:

Making Custom Dialog Boxes- 9

Added lines of info

Supplying the Variable Text

The data is supplied by LISP code. Here we look at how to handle 3D coordinates and text.

Recall that LISP returns the value of points as a list of three numbers, like this:

(1.0000 2.0000 3.0000)

The numbers represent the x, y, and z coordinates, respectively. We need to convert the list of three numbers to a string --- why does it have to be so hard?! Use the following code, which assumes that variable lpt contains (1.0000 2.0000 3.0000):

(car lpt)

The car function extracts the x-coordinate from the list as a real number, such as 1.0000. Similarly:

(cadr lpt)

(caddr lpt)

The cadr and caddr functions extract the y (2.0000) and z (3.0000) coordinates, respectively. To convert the real numbers to strings, use the rtos function, as follows:

(rtos (car lpt))

(rtos (cadr lpt))

(rtos (caddr lpt))

And then to combine the three individual strings into one string, use the strcat (string concatenation) function, as follows:

(strcat

(rtos (car lpt))

(rtos (cadr lpt))

(rtos (caddr lpt))

)

This code displays 1.000 2.000 3.000. It would be a nice touch to put commas between the numbers:

(strcat

(rtos (car lpt)) ","

(rtos (cadr lpt)) ","

(rtos (caddr lpt))

)

Put both lines of code together, and we arrive at the LISP needed to implant the value of the LastPoint system variable in the dialog box:

(setq lpt (getvar "lastpoint"))

(set_tile "lastPointData" (strcat (rtos (car lpt)) "," (rtos (cadr lpt)) "," (rtos )

(caddr lpt))))

Add the code to the xx.lsp, and then run it in BricsCAD to see the result.

Making Custom Dialog Boxes- 10

Adding the last point data

Leaving Room for Variable Text

Oops, the numbers are cut off. BricsCAD sizes the dialog box is sized before the LISP code inserts the data, so it doesn't know that the dialog box needs to be bigger to accommodate the x,y,z coordinates --- which can run to many characters in length.

The solution is to use the width attribute for each text tiles, like this:

: text {

value = "";

key = "lastAngleData";

width = 33;

}

When added to the DCL file, the result looks like this:

Making Custom Dialog Boxes- 11

Adding last x,y point data

FIXING THE BUTTON WIDTH

Oops. Now the  OK button is too wide. To make it narrower (i.e., fix its width), use the fixed_width attribute in the DCL file:

fixed_width = true;

By setting it to true, the button is made only as wide as the label.

Making Custom Dialog Boxes- 12

More changes

Centering the Button

Oops! Now the button is no longer centered. By default, the button is left-justified. To center it, use the alignment attribute:  alignment = centered;

Add the new code to the button portion of the DCL file...

: button {

key = "okButton";

label = "OK";

is_default = true;

alignment = centered;

fixed_width = true;

}

...and then rerun the xx.lsp file to see that the properly-sized OK button is centered.

Making Custom Dialog Boxes- 13

TESTING THE DIALOG BOX

It's always a good idea to test the dialog box under a number of situations. Use the Line command
to draw a few lines. This action changes the values of the three sysvars. Re-run the xx.lsp routine.
The values displayed by the dialog box should be different.

Making Custom Dialog Boxes- 14

Properly formatted dialog box

Defining the Command

So far, you've been running xx.lsp to develop and test the dialog box. Now that it's running properly, you should change the "xx" name to one that is more descriptive. Rename the LISP file as last.lsp, change the function name inside to C:last, and make the variables local, as follows:

(defun c:last (/ dlg-id lang lpt lcmd)

(setq dlg-id (load_dialog "c:\lastInput"))

(new_dialog "lastInput" dlg-id)

(setq lang (getvar "lastangle"))

(set_tile "lastAngleData" (rtos lang))

(setq lpt (getvar "lastpoint"))

(set_tile "lastPointData" (strcat (rtos (car lpt)) "," (rtos (cadr lpt)) ","

(rtos (caddr lpt))))

(setq lcmd (getvar "lastprompt"))

(set_tile "lastPromptData" lcmd)

(action_tile "okButton" "(done_dialog)")

(start_dialog)

(unload_dialog dlg-id)

)

The DCL file looks like this, in its entirety:

lastInput: dialog {

label = "Last Input";

: row {

: text {

label = "Last angle: ";

key = "lastAngle";

}

: text {

value = "";

key = "lastAngleData";

width = 33;

}

}

: row {

: text {

label = "Last point: ";

key = "lastPoint";

}

: text {

value = "";

key = "lastPointData";

width = 33;

}

}

: row {

: text {

label = "Last prompt: ";

key = "lastPrompt";

}

: text {

value = "";

key = "lastPromptData";

width = 33;

}

}

: button {

key = "okButton";

label = "OK";

is_default = true;

alignment = centered;

fixed_width = true;

}

}

If you would like to have this command loaded automatically each time you start BricsCAD, add last.lsp to the AppLoad command's startup list.

Examples of DCL Tiles

With the basic tutorial behind you, let's take a look at how to code other types of dialog box features.

In this last part of this post, we look at how to code the following tiles:

Recall that two pieces of code are always required:

  1. The DCL code that specifies the layout of the dialog box
  2. The LISP code that activates the dialog box.

BUTTONS

In the preceding tutorial, you coded an OK button that allowed you to exit the dialog box. It turns out that you don't need to do the coding because BricsCAD codes a number of buttons and other dialog box elements for you. These are found in a file called base.dcl that is normally loaded into BricsCAD automatically.

Making Custom Dialog Boxes- 15

The names of the pre-built tiles are:

Prebuilt Tile Button(s) Displayed
ok_only OK
ok_cancel OK Cancel
ok_cancel_help OK Cancel Help
ok_cancel_help_info OK Cancel Help Info . . .
Ok_Cancel_Help_Errtile OK Cancel Help, plus space for error messages.

Use these prebuilt tiles to ensure a consistent look for your dialog boxes. Here is an example of how to use these buttons in DCL files:

ok_only;

It's just that easy!

Notice that the tile name lacks the traditional colon ( : ) prefix, but does require the semicolon ( ; ) terminator.

DCL allows you to create buttons that have labels made of text (button tiles) or images (image_button tiles).

To indicate that the button opens another dialog box, use an ellipsis ( ... ), such as Info....

In addition to text and image buttons, settings can be changed with checkboxes (toggle tiles) and radio buttons (radio_button tiles) as described next.

Making Buttons Work

OK and Cancel are easy, because their functions are already defined. It's one thing to populate a dialog box with buttons; it's another to have them execute commands.

Let's see how to make buttons execute commands. In this tutorial, you will create a dialog box with Plot and Preview buttons. The figure below shows how it will look in Linux (it looks similar in Windows).

Making Custom Dialog Boxes- 16

The purpose of the Plot button is to execute the Plot command, and of Preview button to execute the Preview command.

The easy solution would be to add an action attribute to each button to execute a LISP function, such as (command "plot").  But we cannot because DCL does not allow the highly-useful command function to be used in the action attribute!

The key to solving the problem is the key attribute. It gives buttons identifying names by which LISP functions can reference them, such as:

key = "plot";

Then, over in the LISP file, you use the action_tile function to execute the Plot command. Well, not quite. It has the same restriction against use of the command function, so you must approach this indirectly by getting action_tile to refer to a second LISP routine, such as (action_tile "plot" "(cmd-plot)").

But even this will not work because you need your custom dialog box to disappear from the screen, and be replaced by the Plot dialog box. The solution is to become even more indirect:

(action_tile "plot" "(setq nextDlg 1) (done_dialog)")

"plot" --- identifies the Plot button through its key, "plot".

(setq nextDlg 1) --- records that the user clicked the Plot button for further processing later on.

(done_dialog) --- closes the dialog box.

This is done twice, once each when the user clicks the Plot button or the Preview button. The Preview button's code is similar; changes are shown in boldface:

(action_tile "preview" "(setq nextDlg 2) (done_dialog)")

Then, you need some code that decides what to do when nextDlg is set to 1 or 2:

(if (= nextDlg 1) (cmd-plot))

(if (= nextDlg 2) (cmd-preview))

When nextDlg = 1, then the following subroutine is run:

(defun cmd-plot ()

(command "print")

)

When nextDlg = 2, then the following subroutine is run instead:

(defun cmd-preview ()

(command "preview")

)

With the planning behind us, let's look at all the code. First, in the x.dcl file, you add the key attributes to each button. The code that relates to the Plot button is shown boldface, while Preview-related code is shown in color:

x: dialog { label = "Plot";

: row {

: button { label = "Plot"; mnemonic = "P"; key = plot; }

: button { label = "Preview"; mnemonic = "v"; key = "preview"; }

cancel_button;

} }

Second, in the xx.lsp file, you add the code that executes the Plot and Preview commands.

(defun c:xx (/)

(setq dlg-id (load_dialog "c:\x"))

(new_dialog "x" dlg-id)

(action_tile "plot" "(setq nextDlg 1) (done_dialog)")

(action_tile "preview" "(setq nextDlg 2) (done_dialog)")

(start_dialog)

(unload_dialog dlg-id)

(if (= nextDlg 1) (cmd-plot))

(if (= nextDlg 2) (cmd-preview))

)

(defun cmd-plot ()

(command "print")

)

(defun cmd-preview ()

(command "preview")

)

In Linux, remember to remove the "c:\" so that the load_dialog line reads as follows:

(setq dlg-id (load_dialog "x"))

Making Custom Dialog Boxes- 17

When the dialog box appears, click each button to ensure it executes the related command.

Checkboxes

Checkboxes allow you to have one or more options turned on. They contrast to *radio buttons*, which limit you to a single choice. Checkboxes are created by the toggle tile.

In this tutorial, you will create a checkbox that changes the shape of point objects. This is accomplished by changing the value of the PdMode system variable. Yes, there is the DdPType command that does the same thing, but this is a different approach, as you will see.

The PdMode system variable can take these values:

  • 0 Dot ( . )
  • 1 Nothing
  • 2 Plus ( + )
  • 3 Cross ( x )
  • 4 Short vertical line ( | )
  • 32 Circle
  • 64 Square

In addition, these numbers can be combined through addition. For example, 34 (32 + 2) adds a circle (32) to the plus symbol (2).

Making Custom Dialog Boxes- 19

Left to right: PdMode = 32, 33, and 34.

Here is a peculiarity to points to be aware of: 32 actually a circle with dot (32 + 0), because 0 draws a dot. In comparison, 33 (32 + 1) is the circle alone, because the 1 displays nothing!

Let's see how to create a dialog box that lets us select combinations of the plus, circle, and square point symbols. How about a dialog box that looks something like this...

Making Custom Dialog Boxes- 20-800x316

Here is the code needed to generate the dialog box:

x: dialog { label = "Point Style";

: column { label = "Select a point style: " ;

: toggle { key = "plus" ; label = "Plus" ; value = "1" ; }

: toggle { key = "circle" ; label = "Circle" ; }

: toggle { key = "square" ; label = "Square" ; }

}

ok_cancel;

}

Notice that value = "1" turns on the Plus option (to show the checkmark), making it the default value.

Now let's write the LISP file to make the dialog box work. Something as simple as (action_tile "plus" "(setvar "pdmode" 2)") doesn't work because the user might want to select more than one option --- which is the whole point of toggles. You need the code to go through three steps:

Step 1: Read which option(s) users have checked.
Step 2: Add up the setting(s).
Step 3: Set PdMode to show the desired point style.

Let's implement it:

  1. To read user input from dialog boxes, employ LISP's $value variable for the Plus toggle:

    (action_tile "plus" "(setq plusVar $value)")

    Repeat the code for the other two toggles, Circle and Square:

    (action_tile "circle" "(setq circleVar $value)")

    (action_tile "square"(setq squareVar $value)")

  2. The $value variable contains just 1s and 0s. Later, we will use a lookup table to convert the 1s and 0s into the values expected by PdMode. For instance, if Plus is selected ("1"), then PdMode expects a value of 2. The lookup table uses the if function to correct the numbers, as follows:

    (if (= plusVar "1") (setq plusNum 2) (setq plusNum 0))

    This can be read as:

    If plusVar = 1, then set plusNum = 2;

    otherwise, set plusNum = 0.

    Repeat the lookup code for the other two toggles, Circle and Square:

    (if (= squareVar "1") (setq squareNum 64) (setq squareNum 0))

    (if (= circleVar "1") (setq circleNum 32) (setq circleNum 0))
    fh


    TIP The $value retrieved by get_tile is actually a string, like "1". The PdMode system variable, however, expects an integer. Thus, the lookup table performs a secondary function of converting strings to integers.


    With the values set to what PdMode expects, add them up with the + function:

    (setq vars (+ plusNum circleNum squareNum))

  3. To change the value of PdMode, you employ LISP's setvar function, like this:

    (setvar "pdmode" vars)

    Here is all of the LISP code:

    (defun c:xx (/)

    (setq dlg-id (load_dialog "c:\x"))

    (new_dialog "x" dlg-id)

    ;; Get the current values from each toggle tile:

    (setq plusVar (get_tile "plus"))

    (setq circleVar (get_tile "circle"))

    (setq squareVar (get_tile "square"))

    ;; See which toggles the user clicks:

    (action_tile "plus" "(setq plusVar $value)")

    (action_tile "circle" "(setq circleVar $value)")

    (action_tile "square" "(setq squareVar $value)")

    (start_dialog)

    (unload_dialog dlg-id)

    ;; Lookup table converts "0"/"1" strings to the correct integers:

    (if (= plusVar "1") (setq plusNum 2) (setq plusNum 0))

    (if (= circleVar "1") (setq circleNum 32) (setq circleNum 0))

    (if (= squareVar "1") (setq squareNum 64) (setq squareNum 0))

    ;; Add up the integers, and then change system variable

    (setq vars (+ plusNum circleNum squareNum))

    (setvar "pdmode" vars)

    )

Radio Buttons

Radio buttons are easier to code than toggles, because only one can be active at a time.
In this tutorial, you will create a dialog box that uses radio buttons to change the isoplane. The dialog box changes the value of the SnapIsoPair system variable, which takes the following values:

  • 0 Left isoplane (default)
  • 1 Top isoplane
  • 2 Right isoplane

To make a dialog box that looks like this...

Making Custom Dialog Boxes- 21

... takes this code:

x: dialog { label = "Isoplane";

: column { label = "Change the isoplane to: " ;

: radio_button { key = "left" ; label = "Left isoplane" ; value = "1" ; }

: radio_button { key = "top" ; label = "Top isoplane" ; }

: radio_button { key = "right" ; label = "Right isoplane" ; }

spacer;

}

ok_cancel;

}

Notice that value = "1" turns on the X for the checkbox next to Left.

Before going on to the accompanying LISP file, first set up BricsCAD to display isometric mode:

  1. Enter the Settings command.
  2. In the Search field, enter "Snap Style."Making Custom Dialog Boxes- 23
  3. In the Snap Type droplist, select Isometric snap.
  4. Click X to dismiss the dialog box.

BricsCAD is now in isometric mode.

As you use the dialog box described below, the cursor changes its orientation:

Making Custom Dialog Boxes- 24

Left to right: Cursor for the left, top, and right isoplanes.

Let's now turn to the LISP file that will make this dialog box work. It is similar to the code used for
toggles; the primary difference is that values are not added together:

(defun c:xx (/)

(setq dlg-id (load_dialog "c:\x"))

(new_dialog "x" dlg-id)

;; See which radio button the user clicks:

(action_tile "left" "(setq leftVar $value)")

(action_tile "top" "(setq topVar $value)")

(action_tile "right" "(setq rightVar $value)")

(start_dialog)

(unload_dialog dlg-id)

;; Lookup table:

(if (= leftVar "1") (setq vars 0))

(if (= topVar "1") (setq vars 1))

(if (= rightVar "1") (setq vars 2))

;; Change system variable:

(setvar "snapisopair" vars)

)

We have been cheating a bit because we are forcing the dialog box to show the Left isoplane as the default. This is not necessarily true. You really should modify the DCL and LISP code to make the dialog box initially show the default isoplane --- whether left, top, or right.

Setting the default is done with LISP's set_tile function. First, change the DCL code so that it no longer makes the Left isoplane the default: change* value = "1" *to:

value = ""

In the LISP code, you need to do the following: (a) extract the value of SnapIsoPair with getvar, and then (b) use set_tile as a callback.

  1. Extract the current value of SnapIsoPair with the getvar function:

    getvar "snapisopair"))

  2. Set the default button with the set_tile function:

    set_tile "left" "1"))

This reads, as follows:

If the value of SnapIsoPair is 0 (= vars 0),

then turn on the Left isoplane radio button (set_tile "left" "1").

Write similar code for the other two radio buttons:

(if (= vars 1) (set_tile "top" "1"))

(if (= vars 2) (set_tile "right" "1"))

The other change you need to make is to change some of the variables to local:

(defun c:xx (/ leftVar topVar rightVar)

This forces the three variables to lose their value when the LISP routine ends. Otherwise, rightVar keeps its value (it's the last one) and makes Right isoplane the default each time the dialog box is opened.

With these changes in place, the improved code looks like this --- with changes highlighted in boldface:

(defun c:xx (/ leftVar topVar rightVar)

(setq vars (getvar "snapisopair"))

(setq dlg-id (load_dialog "c:\x"))

(new_dialog "x" dlg-id)

;; Set the default button:

(if (= vars 0) (set_tile "left" "1"))

(if (= vars 1) (set_tile "top" "1"))

(if (= vars 2) (set_tile "right" "1"))

;; See which radio button the user clicks:

(action_tile "left" "(setq leftVar $value)")

(action_tile "top" "(setq topVar $value)")

(action_tile "right" "(setq rightVar $value)")

(start_dialog)

(unload_dialog dlg-id)

;; Lookup table:

 (if (= leftVar "1") (setq vars 0))

(if (= topVar "1") (setq vars 1))

(if (= rightVar "1") (setq vars 2))

;; Change system variable:

(setvar "snapisopair" vars)

)

Now each time the dialog box starts, it correctly displays the default isoplane, such as "Right," as illustrated below:

Making Custom Dialog Boxes- 25

CLUSTERS

Clusters help you combine related groups of controls. DCL lets you specify vertical, horizontal, boxed, and unboxed clusters. In addition, radio clusters are required when you want to have two radio buttons on at the same time. In all other cases, clusters are needed only for visual and organizational purposes.

BricsCAD makes it look as if there are eight tiles for making clusters:

Column Row
Boxed_Column Boxed_Row
Radio_Column Radio_Row
Boxed_Radio_Column Boxed_Radio_Row

But these eight can be reduced to three, when you take the following into account:

  • The column tile is usually not needed, because BricsCAD automatically stacks tiles vertically into columns.
  • The column and row tiles display a box as soon as you include a label for them.
  • Tiles with radio in their names are only for clustering radio buttons.

Columns and Rows

BricsCAD normally stacks tiles, so no column tile is needed, as illustrated by this DCL code:

x: dialog {

: button { label = "&Button"; }

: button { label = "&Click"; }

: button { label = "&Pick"; }

ok_only;

}

Making Custom Dialog Boxes- 24-768x412

(The ampersand --- & --- specifies the shortcut keystroke that accesses the button from the keyboard with the Alt key, such as pressing Alt+B.)

To create a horizontal row of tiles, use the row {} tile, as shown in boldface below:

x: dialog {

: row {

: button { label = "&Button" ;}

: button { label = "&Click"; }

: button{ label = "&Pick" ;}

}

ok_only;

}

The boxing of the horizontal row is invisible, so I highlighted it with a blue rectangle.

Making Custom Dialog Boxes- 26

Because the ok_only tile is outside of the row {} tile, it is located outside of the cluster, stacked vertically below the row of three buttons.

Boxed Row

To actually show a rectangle (box) around the three buttons, change "row" to boxed_row, as follows:

x: dialog {

: boxed_row {

// et cetera

}

ok_only;

}

Making Custom Dialog Boxes- 27

Boxed Row with Label

You can add text to describe the purpose of the boxed buttons with the label attribute, as shown in boldface below:

x: dialog {

: boxed_row { label = "Three Buttons";

// et cetera

}

ok_only;

}

The curious thing is that you get the same effect whether using the boxed_row or row tile. That's right: when you add a label to the row tile, BCL automatically adds a box around the cluster.

Making Custom Dialog Boxes- 28

To eliminate the box, precede the row with the text tile for the title, as follows:

x: dialog {

: text { label = "Three Buttons";}

: row {

// et cetera

}

ok_only;

}

Making Custom Dialog Boxes- 29

Special Tiles for Radio Buttons

You can use the regular row and column tiles with radio buttons, except in one case: when more than one radio button needs to be turned on. Recall that only one radio button can be on (shown the black dot) at a time; BricsCAD automatically turns off all other radio buttons that might be set to on (value = "1").

The solution is to use two or more radio_column tiles, each holding one of the radio button sets that need to be on.

It is not recommended to use rows for radio buttons, because this horizontal configuration is psychologically more difficult for users.

Debugging DCL

The most common DCL coding errors are due to errors in punctuation, such as leaving out a closing semi-colon or quotation mark. These problems are announced by error-message dialog boxes, which I illustrate later in this section.

DCL_SETTINGS

DCL contains a debugger for finding certain coding errors. To activate the debugger, add the audit_level parameter to the beginning of the DCL file, before the dialog tile:

dcl_settings : defalut_dcl_settings { audit_level = 3 ; }   x : dialog { // et cetera

The debugger operates at four levels:

Audit Level Meaning
0 No debugging performed
1 (Default) Checks for DCL errors that may terminate BricsCAD, such as undefined tiles or circular prototype definitions
2 Checks for undesirable layouts and behaviors such as missing attributes or wrong attribute values
3 Checks for redundant attribute definitions

DCL ERROR MESSAGES

BricsCAD displays DCL-related error messages in dialog boxes. You may encounter some of the following:

Semantic error(s) is DCL file

Sometimes an error dialog box suggests that you look at the *acad.dce *file --- the DCL error file. The problem is that the dialog box doesn't tell you where this file is located. After running Windows Search on my computer's C: and D: drives, I finally found the file in the D:\documents and settings\ administrator\my documents folder.

The file contains information about errors, such as:

====== DCL semantic audit of c:\x ======

Error. Widget named "asdfasfads" is undefined.

It's not clear to me why some errors are displayed directly in the message dialog boxes, while others are stored in the acad.dce file.

Dialog has neither an OK nor a CANCEL button

Dialog boxes need to exit through an OK or Cancel button. At the very least, add the ok_only tile to the DCL file. DCL was written before Windows automatically added the x (cancel) button to all dialog boxes, and Autodesk has failed to update DCL to take this innovation into account.

Error in dialog file "filename.dcl", line n

Your DCL file contains the name of a tile unknown to BricsCAD. Check its spelling. In this example, ok_only was prefixed by a colon ( : ), which is incorrect for prebuilt tiles.

Incorrect:      : ok_only ;

Correct:         ok_only ;

Dialog too large to fit on screen

A tile in the DCL file is creating a dialog box that would not fit your computer's screen. This can happen when the edit_edith, width, or height attributes are too large.

Additional Resources

There is more to learn about writing dialog boxes with DCL, such as through these DCL tutorials:

AfraLisp at sports tutorialsMaking Custom Dialog Boxes- 30

including these topics:

  • Getting Started
  • DCL Primer -- Download
  • Dialog Box Layout
  • Dialogue Boxes Step by Step
  • Dialogue Boxes in Action
  • Nesting and Hiding Dialogues
  • Hiding Dialogues Revisited
  • LISP Message Box
  • LISP Input Box
  • Referencing DCL Files
  • LISP Functions for Dialog Control Language (DCL)
  • Functional Synopsis of DCL Files
  • DCL Attributes
  • Dialogue Box Default Data
  • DCL Model
  • DCL Progress Bar
  • Attributes and Dialogue Boxes
  • DCL without the DCL File
  • The AfraLisp DCL Tutorials
  • Entering Edit Boxes