For this week’s blog we are going to create a simple calculator using Legato. Legato offers a wide variety of math functions that we can use to program the calculator. Before we get started coding, let’s think about the parts of the script we will need. First there will need to be a user interface. In our case, a dialog works perfectly. Secondly, we need to have a way to store operands, and a floating point number is good choice here. If we use only an integer the user cannot do decimal math. Finally, we will need to have operation processing. For this we will use Legato’s math functions. To begin I am going to set up the basics of the dialog, and we can add the processing together.
Friday, May 18. 2018
LDC #85: Math Functions in Legato
Here’s the base script with its resources:
// // Globals // ------- #define LBL_SQ "x ²" #define LBL_SQ_SEC "x ³" #define LBL_POW "x^y" #define LBL_POW_SEC "nthrt" #define LBL_SQRT "sqrt" #define LBL_SQRT_SEC "1/x" #define LBL_SIN "sin" #define LBL_SIN_SEC "asin" #define LBL_COS "cos" #define LBL_COS_SEC "acos" #define LBL_TAN "tan" #define LBL_TAN_SEC "atan" #define LBL_10TO "10^x" #define LBL_10TO_SEC "e^x" #define LBL_LOG "log" #define LBL_LOG_SEC "ln" #define ZERO_ERROR "Cannot divide by zero" // // Math Processing // --------------- string cal_run_operation(float val) { return ""; } // // Supporting Dialog and Main Entry // -------------------------------- #define DC_BTN_N0 100 #define DC_BTN_N1 101 #define DC_BTN_N2 102 #define DC_BTN_N3 103 #define DC_BTN_N4 104 #define DC_BTN_N5 105 #define DC_BTN_N6 106 #define DC_BTN_N7 107 #define DC_BTN_N8 108 #define DC_BTN_N9 109 #define DC_BTN_NDEC 110 #define DC_BTN_NINV 111 #define DC_BTN_NPI 112 #define DC_BTN_OADD 113 #define DC_BTN_OSUB 114 #define DC_BTN_OMUL 115 #define DC_BTN_ODIV 116 #define DC_BTN_OEQL 117 #define DC_BTN_OSQ 118 #define DC_BTN_OPOW 119 #define DC_BTN_OSIN 120 #define DC_BTN_OCOS 121 #define DC_BTN_OTAN 122 #define DC_BTN_OSQRT 123 #define DC_BTN_O10TO 124 #define DC_BTN_OLOG 125 #define DC_BTN_OMOD 126 #define DC_BTN_CBACK 127 #define DC_BTN_C 128 #define DC_BTN_CCE 129 #define DC_BTN_C2 130 #define DC_BTN_CMC 131 #define DC_BTN_CMR 132 #define DC_BTN_CMPLS 133 #define DC_BTN_CMSUB 134 #define DC_BTN_CMS 135 #define DC_SCREEN 201 #define DC_OPERATION 202 int main() { DialogBox("CalculatorDlg", "cal_"); return 0; } int cal_load() { return ERROR_NONE; } int cal_action(int id, int action) { return ERROR_NONE; } int cal_validate() { return ERROR_NONE; } #beginresource CalculatorDlg DIALOGEX 0, 0, 136, 193 EXSTYLE WS_EX_DLGMODALFRAME STYLE DS_MODALFRAME | DS_3DLOOK | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU CAPTION "Legato Calculator" FONT 8, "MS Shell Dlg" { CONTROL " 0 &0", DC_BTN_N0, "button", BS_PUSHBUTTON | BS_LEFT | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 55, 169, 25, 20, 0 CONTROL " 1 &1", DC_BTN_N1, "button", BS_PUSHBUTTON | BS_LEFT | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 30, 149, 25, 20, 0 CONTROL " 2 &2", DC_BTN_N2, "button", BS_PUSHBUTTON | BS_LEFT | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 55, 149, 25, 20, 0 CONTROL " 3 &3", DC_BTN_N3, "button", BS_PUSHBUTTON | BS_LEFT | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 80, 149, 25, 20, 0 CONTROL " 4 &4", DC_BTN_N4, "button", BS_PUSHBUTTON | BS_LEFT | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 30, 129, 25, 20, 0 CONTROL " 5 &5", DC_BTN_N5, "button", BS_PUSHBUTTON | BS_LEFT | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 55, 129, 25, 20, 0 CONTROL " 6 &6", DC_BTN_N6, "button", BS_PUSHBUTTON | BS_LEFT | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 80, 129, 25, 20, 0 CONTROL " 7 &7", DC_BTN_N7, "button", BS_PUSHBUTTON | BS_LEFT | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 30, 109, 25, 20, 0 CONTROL " 8 &8", DC_BTN_N8, "button", BS_PUSHBUTTON | BS_LEFT | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 55, 109, 25, 20, 0 CONTROL " 9 &9", DC_BTN_N9, "button", BS_PUSHBUTTON | BS_LEFT | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 80, 109, 25, 20, 0 CONTROL " ÷ &/", DC_BTN_ODIV, "button", BS_PUSHBUTTON | BS_LEFT | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 105, 89, 25, 20, 0 CONTROL " × &*", DC_BTN_OMUL, "button", BS_PUSHBUTTON | BS_LEFT | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 105, 109, 25, 20, 0 CONTROL " - &-", DC_BTN_OSUB, "button", BS_PUSHBUTTON | BS_LEFT | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 105, 129, 25, 20, 0 CONTROL " + &+", DC_BTN_OADD, "button", BS_PUSHBUTTON | BS_LEFT | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 105, 149, 25, 20, 0 CONTROL " = &=", DC_BTN_OEQL, "button", BS_PUSHBUTTON | BS_LEFT | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 105, 169, 25, 20, 0 CONTROL " . &.", DC_BTN_NDEC, "button", BS_PUSHBUTTON | BS_LEFT | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 80, 169, 25, 20, 0 CONTROL "±", DC_BTN_NINV, "button", BS_PUSHBUTTON | BS_CENTER | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 30, 169, 25, 20, 0 CONTROL "CE", DC_BTN_CCE, "button", BS_PUSHBUTTON | BS_CENTER | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 30, 89, 25, 20, 0 CONTROL "C", DC_BTN_C, "button", BS_PUSHBUTTON | BS_CENTER | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 55, 89, 25, 20, 0 CONTROL "\xd5", DC_BTN_CBACK, "button", BS_PUSHBUTTON | BS_CENTER | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 80, 89, 25, 20, 0 CONTROL LBL_SQ, DC_BTN_OSQ, "button", BS_PUSHBUTTON | BS_CENTER | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 5, 46, 25, 20, 0 CONTROL LBL_POW, DC_BTN_OPOW, "button", BS_PUSHBUTTON | BS_CENTER | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 30, 46, 25, 20, 0 CONTROL LBL_SIN, DC_BTN_OSIN, "button", BS_PUSHBUTTON | BS_CENTER | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 55, 46, 25, 20, 0 CONTROL LBL_COS, DC_BTN_OCOS, "button", BS_PUSHBUTTON | BS_CENTER | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 80, 46, 25, 20, 0 CONTROL LBL_TAN, DC_BTN_OTAN, "button", BS_PUSHBUTTON | BS_CENTER | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 105, 46, 25, 20, 0 CONTROL LBL_SQRT, DC_BTN_OSQRT, "button", BS_PUSHBUTTON | BS_CENTER | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 5, 66, 25, 20, 0 CONTROL LBL_10TO, DC_BTN_O10TO, "button", BS_PUSHBUTTON | BS_CENTER | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 30, 66, 25, 20, 0 CONTROL LBL_LOG, DC_BTN_OLOG, "button", BS_PUSHBUTTON | BS_CENTER | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 55, 66, 25, 20, 0 CONTROL "pi", DC_BTN_NPI, "button", BS_PUSHBUTTON | BS_CENTER | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 80, 66, 25, 20, 0 CONTROL "Mod", DC_BTN_OMOD, "button", BS_PUSHBUTTON | BS_CENTER | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 105, 66, 25, 20, 0 CONTROL "2nd", DC_BTN_C2, "button", BS_CHECKBOX | BS_PUSHLIKE | BS_CENTER | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 5, 89, 20, 13, 0 CONTROL "MC", DC_BTN_CMC, "button", BS_PUSHBUTTON | BS_CENTER | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 5, 124, 20, 13, 0 CONTROL "MR", DC_BTN_CMR, "button", BS_PUSHBUTTON | BS_CENTER | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 5, 137, 20, 13, 0 CONTROL "M+", DC_BTN_CMPLS, "button", BS_PUSHBUTTON | BS_CENTER | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 5, 150, 20, 13, 0 CONTROL "M-", DC_BTN_CMSUB, "button", BS_PUSHBUTTON | BS_CENTER | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 5, 163, 20, 13, 0 CONTROL "MS", DC_BTN_CMS, "button", BS_PUSHBUTTON | BS_CENTER | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 5, 176, 20, 13, 0 CONTROL "", DC_OPERATION, "edit", ES_RIGHT | ES_READONLY | WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP, 2, 4, 128, 10, 0 CONTROL "", DC_SCREEN, "edit", ES_RIGHT | ES_READONLY | WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP, 2, 16, 128, 20, 0 } #endresource
The script contains a dialog resource and a main routine to show the dialog. It also has some defines for language used on the dialog as well as the control identifiers.
If we run the script as it is now, a dialog will open. None of the buttons will do anything, but we can hit the X button in the corner to close it. You may have also noticed that one of the buttons has a weird character. This is because we are going to use the Wingdings font for that button. Also, some of the buttons have a lot of spaces in their text, for example, “ × &*”. This is because the buttons are left aligned. We are using the spaces to “center” the text of the button. The rest of the text is hidden, but that hidden text contains the keyboard shortcut keys (a preceding ampersand makes the next character the shortcut). This means the user can type numbers using the keyboard and even use * and / for multiplication and division.
Now that we have our base user interface, let’s start adding our global variables. We are going to need an operand, operation, memory (for the calculator memory functions), and a way to track whether the user is entering numbers. We are also going to need the last operation and value if we want to have the equal button run the same operation again. Lastly, we need a boolean value to support the “2nd” button that changes the operations buttons.
Let’s first tackle the operand. As stated above, using an integer value means we cannot process decimal amounts, so that’s not ideal. We could also store the result as a string. This would allow us to have unlimited precision but it means we would need to convert to numeric values if we wanted to perform any operations. For our purposes, the easiest solution is to use a float value. We need two of them: one for the operand and one for the memory.
The boolean values are straight forward. For the last value used by the repeating equal button, we can use a string to simplify code although another float would have worked. The easiest way to store the operation is to store the ID of the button the user pressed. That way the global variables for the operation and last operation are integer values.
Thus we can define our global variables as follows:
// // Globals // ------- float gbl_operand; float gbl_memory; string gbl_last_val; boolean gbl_entering; boolean gbl_second; int gbl_operation; int gbl_last_operation; #define LBL_SQ "x ²" ... int main() { gbl_operand = 0; gbl_memory = 0; gbl_last_val = ""; gbl_entering = false; gbl_second = false; gbl_operation = 0; gbl_last_operation = 0; DialogBox("CalculatorDlg", "cal_"); return 0; } ...
Okay, we have our variables and their initialization in the main function. Let’s quickly wrap up the load function to improve the look of the UI before digging into the complexities of the calculator. We need to set the font for the “screen” and the backspace button. This can be done using the ControlChangeFont function, which simply takes a control ID and the name of a font style. We will use “Mono” for the “screen” and use “Wingdings” for the backspace button.
int cal_load() { ControlChangeFont(DC_OPERATION, "Mono"); ControlChangeFont(DC_SCREEN, "Mono"); ControlChangeFont(DC_BTN_CBACK, "Wingding"); EditSetText(DC_SCREEN, "0"); return ERROR_NONE; }
So what do we do next? The action part of the dialog will allow us to test our work as we go along. That makes it a good place to start our design. The action function is going to be split into four parts. The first will be processing for calculator commands (such as the memory buttons and clear). The second will be the processing for the equals button. This part is the majority of the calculator hence why it’s its own part. The third part of the action function covers the entering of data into the calculator. This includes pressing the “pi” and number buttons. Lastly, we need to process the operation buttons. This is the calculator’s basic functionality for the user, so let’s begin here.
This sounds complicated but we can use a switch statement to cover the majority of the buttons in short order. If a number button hasn’t been pressed yet we need to clear the “screen” and add a number; otherwise we need to add to the screen. We can use the control ID of the button as its value because they are numbered sequentially. Lastly, if the user hit anything other than a number we need to turn off the entering flag.
int cal_action(int id, int action) { string tmp; // Number entering switch(id) { case DC_BTN_N0: case DC_BTN_N1: case DC_BTN_N2: case DC_BTN_N3: case DC_BTN_N4: case DC_BTN_N5: case DC_BTN_N6: case DC_BTN_N7: case DC_BTN_N8: case DC_BTN_N9: if (gbl_entering == true) { tmp = EditGetText(DC_SCREEN); } tmp = tmp + FormatString("%d", id - DC_BTN_N0); EditSetText(DC_SCREEN, tmp); gbl_entering = true; break; default: if (action == BN_CLICKED) { gbl_entering = false; } break; } return ERROR_NONE; }
Now if you run the program, when you press a number key it will add it to the screen. If you press anything else it doesn’t do anything but the next number press will start over again. In and of itself this isn’t overly useful, but it’s a good starting point. Let’s finish up the number entering by added the decimal button, the “±” button, as well as the pi button. These work pretty similarly to the number buttons. The decimal button has an extra check to make sure there isn’t already a decimal on the screen. The “±” button adds or removes the negative sign from the screen. Lastly, the pi button replaces the contents of the screen with the value of pi. Now we have all the processing for data entry.
... case DC_BTN_N9: if (gbl_entering == true) { tmp = EditGetText(DC_SCREEN); } tmp = tmp + FormatString("%d", id - DC_BTN_N0); EditSetText(DC_SCREEN, tmp); gbl_entering = true; break; case DC_BTN_NDEC: if (gbl_entering == true) { tmp = EditGetText(DC_SCREEN); } if (FindInString(tmp, ".") < 0) { tmp = tmp + "."; } EditSetText(DC_SCREEN, tmp); gbl_entering = true; break; case DC_BTN_NINV: tmp = EditGetText(DC_SCREEN); if (tmp != "") { if (tmp[0] == '-') { tmp = GetStringSegment(tmp, 1); } else { tmp = "-" + tmp; } EditSetText(DC_SCREEN, tmp); } gbl_entering = true; break; case DC_BTN_NPI: EditSetText(DC_SCREEN, "3.14159265358"); gbl_entering = false; break; default: if (action == BN_CLICKED) { gbl_entering = false; } break; } ...
At this point we still have to do the control processing, operation processing, and the equals processing. The operation processing is needed to test the equals processing so it is a logical next step. For our purposes, there are two types of operations: operations that take a single operand and operations that take two operands. For example, addition takes two numbers, whereas taking a square root only takes one. Behind the scenes we can process these operations the same way but to the user when they press an operation that takes a single value it should happen instantly, whereas one that takes two values requires more input.
Let’s make a switch statement that contains our operations in the two groups. Then we can add the processing for each group. Also this switch statement is independent of the number itself. In each group we can get the operand using the EditGetText function as well as the DecimalToFloat function. Then we set the gbl_operation variable to our control ID. That way when the user presses equals we will know which operation to process.
int cal_action(int id, int action) { string tmp; // Number entering ... // Operation Processing switch(id) { case DC_BTN_OADD: case DC_BTN_OSUB: case DC_BTN_OMUL: case DC_BTN_ODIV: case DC_BTN_OPOW: case DC_BTN_OMOD: gbl_operand = DecimalToFloat(EditGetText(DC_SCREEN)); gbl_operation = id; break; case DC_BTN_OSQ: case DC_BTN_OSIN: case DC_BTN_OCOS: case DC_BTN_OTAN: case DC_BTN_OSQRT: case DC_BTN_OLOG: case DC_BTN_O10TO: gbl_operand = DecimalToFloat(EditGetText(DC_SCREEN)); gbl_operation = id; break; } return ERROR_NONE; }
For the single value operations the remaining code is simple. We can run our own action function again using the equals button for the ID. This acts as if the user pressed the operation and then hit equals button. From the user’s standpoint the operation is instant but from a design standpoint it means we can use the equals button to process all operations, which simplifies code. That’s always a good thing.
... case DC_BTN_OLOG: case DC_BTN_O10TO: gbl_operand = DecimalToFloat(EditGetText(DC_SCREEN)); gbl_operation = id; cal_action(DC_BTN_OEQL, action); break; ...
The operations that take two operands will actually work as written but when the user hits an operation the screen keeps the first value. There is also no indication that an operation has been started. So the code we are going to add is mostly to help the interface. We will start by getting the text of the operation button. We can use this to show the user the name of the operation. Since we have some fancy shortcuts in our button text, we will need to remove all that information. After that is done we can set the result to the “operation screen”. This is all the code in blue below. The code in purple below clears the “screen”. Since our number entry switch has processing in it that turns off entry mode when any other button is pressed, we don’t need to do anything special here to deal with that condition.
... case DC_BTN_OPOW: case DC_BTN_OMOD: gbl_operand = DecimalToFloat(EditGetText(DC_SCREEN)); gbl_operation = id; tmp = EditGetText(id); if (FindInString(tmp, " ") == 0) { tmp = GetStringSegment(tmp, 4, 3); tmp = TrimString(tmp); } tmp = ReplaceInString(tmp, "x", ""); tmp = ReplaceInString(tmp, "&", ""); tmp = ReplaceInString(tmp, "y", ""); tmp = EditGetText(DC_SCREEN) + tmp; EditSetText(DC_OPERATION, tmp); EditSetText(DC_SCREEN, ""); break; ...
If we run the calculator now, entering an operation that requires two operands will show the operation in the “operation screen” and the single operand operations do nothing. Let’s add the processing for the equals button. We also want to be nice to the user and add processing for partial operations. For example, if the user enters 1 + 5 + 3, the operation of 1 + 5 should be executed automatically before + 3 is resolved. The user shouldn’t need to hit equals after each operations; that would be a little tedious. The partial operation processing involves checking if the user hit an operation button (other than equals) while an operation is pending. If that is the case we can run the equals operation and then let the button press happen as normal. The logic for this is simplified by having the control identifiers in groups. All the operation buttons are next to each other so we can check if the control ID is greater than a value instead of looking at individual button identifiers.
int cal_action(int id, int action) { string tmp; // Incomplete Operation Processing if ((id != DC_BTN_OEQL) && (action == BN_CLICKED)) { if ((id > DC_BTN_NPI) && (gbl_operation != 0)) { cal_action(DC_BTN_OEQL, action); } } // Number entering ... return ERROR_NONE; }
Make sure this code block appears before the number and operation processing since it needs to happen before. Now we can do the equals button processing. I made a separate function, cal_run_operation, to actually perform the math. What we need to do is check if there is no operation. If there isn’t that means the user hit equals again. On most calculators, this repeats the last operation, so we will do that by using the gbl_last_value instead of the “screen” content. If there is an operation, we can store the “screen” content in gbl_last_value and run the operation. Afterwards, we check if cal_run_operation returned an error string. If not, we convert the operand into a string and display it on the “screen”. If so, we display the error string. I added a call to the MoneyRemoveTrailingZeros function so the results do not have trailing zeroes. We also store the last operation so we can use it again.
int cal_action(int id, int action) { string tmp; // Sequential Operation Processing if ((id != DC_BTN_OEQL) && (action == BN_CLICKED)) { if ((id > DC_BTN_NPI) && (gbl_operation != 0)) { cal_action(DC_BTN_OEQL, action); } } // Run Operation if (id == DC_BTN_OEQL) { if (gbl_operation == 0) { gbl_operation = gbl_last_operation; } else { gbl_last_val = EditGetText(DC_SCREEN); } tmp = cal_run_operation(DecimalToFloat(gbl_last_val)); gbl_last_operation = gbl_operation; gbl_operation = 0; if (tmp == "") { tmp = FormatString("%.11f", gbl_operand); tmp = MoneyRemoveTrailingZeros(tmp); } EditSetText(DC_SCREEN, tmp); EditSetText(DC_OPERATION, ""); } // Number entering ... return ERROR_NONE; }
So now our equals button does something, or at least it would if we had filled out the cal_run_operation function. Let’s add the calculating to our calculator! To do so we will shift gears into the cal_run_operation function. The action function isn’t complete yet because we still haven’t dealt with the calculator command buttons. At this point we want to get something that at least does something so we can see that our code so far is working.
This function is the meat of the calculator, but it actually is fairly simple. It will again be a big switch statement, this one based on the gbl_operation. We will then run that operation on the gbl_operand and a secondary value, if any. Let’s start with the basic operations.
// // Math Processing // --------------- string cal_run_operation(float val) { switch(gbl_operation) { case DC_BTN_OADD: gbl_operand = gbl_operand + val; break; case DC_BTN_OSUB: gbl_operand = gbl_operand - val; break; case DC_BTN_OMUL: gbl_operand = gbl_operand * val; break; case DC_BTN_ODIV: if (val == 0) { gbl_operand = 0; return ZERO_ERROR; } gbl_operand = gbl_operand / val; break; } return ""; }
As you can see, many of the operations are as simple as running the Legato equivalent (that is to say, using the math operators in Legato). For division, we want to avoid a divide by zero runtime error, so we add an additional check for val. If it is zero, we return our error string define for divide by zero. We also reset the operand since calculator is now in an error state.
If we run the calculator now, we can do basic arithmetic. We can even stack operations without pressing equals and press equals multiple times to repeat operations. We have a calculator! It operates at an elementary level but still it is a calculator. Time to ramp things up! Let’s add in all our operations.
// // Math Processing // --------------- string cal_run_operation(float val) { int i1, i2; switch(gbl_operation) { case DC_BTN_OADD: gbl_operand = gbl_operand + val; break; case DC_BTN_OSUB: gbl_operand = gbl_operand - val; break; case DC_BTN_OMUL: gbl_operand = gbl_operand * val; break; case DC_BTN_ODIV: if (val == 0) { gbl_operand = 0; return ZERO_ERROR; } gbl_operand = gbl_operand / val; break; case DC_BTN_OSQ: gbl_operand = gbl_operand * gbl_operand; break; case DC_BTN_OPOW: gbl_operand = Power(gbl_operand, val); break; case DC_BTN_OSIN: gbl_operand = Sine(gbl_operand); break; case DC_BTN_OCOS: gbl_operand = Cosine(gbl_operand); break; case DC_BTN_OTAN: gbl_operand = Tangent(gbl_operand); break; case DC_BTN_OSQRT: gbl_operand = SquareRoot(gbl_operand); break; case DC_BTN_O10TO: gbl_operand = Power(10, gbl_operand); break; case DC_BTN_OLOG: gbl_operand = Log(gbl_operand, 10); break; case DC_BTN_OMOD: i1 = val; i2 = gbl_operand; gbl_operand = i2 % i1; break; } return ""; }
Now we have power, square, trigonometry, logarithmic, and modulus operations. We can continue to use Legato’s math functions to power our calculator since we used a float as our internal storage. No sense in reinventing the wheel.
So we have a working calculator. However, it is lacking some useful calculator tools such as clear, clear entry, backspace, and memory functions. So let’s go back to the action function and add these features.
We will add another switch block to our action function. In there we can start with clear, clear entry and backspace. Clear is straight forward; we clear the state of the calculator, setting our global variables back to their initial state as well as clearing the “screen”. Clear entry is even simpler as we need to clear only the “screen”. Backspace is a little more complex. We need to get the value of the “screen” and remove the last character but only if the user is currently entering a number. The GetStringSegment function works nicely for this as we can use it to get a part of a string. Also, unlike the other switch statements in this function, we return after we are done processing the button press. This is because some of the calculator command functions don’t affect the state of data entry or the current operation.
int cal_action(int id, int action) { string tmp; // Control Processing switch(id) { case DC_BTN_CBACK: if (gbl_entering == true) { tmp = EditGetText(DC_SCREEN); tmp = GetStringSegment(tmp, 0, GetStringLength(tmp) - 1); EditSetText(DC_SCREEN, tmp); } return ERROR_NONE; case DC_BTN_C: gbl_entering = false; gbl_operand = 0; gbl_operation = 0; EditSetText(DC_OPERATION, ""); EditSetText(DC_SCREEN, ""); return ERROR_NONE; case DC_BTN_CCE: gbl_entering = false; EditSetText(DC_SCREEN, ""); return ERROR_NONE; case DC_BTN_C2: return ERROR_NONE; case DC_BTN_CMC: return ERROR_NONE; case DC_BTN_CMR: return ERROR_NONE; case DC_BTN_CMPLS: return ERROR_NONE; case DC_BTN_CMSUB: return ERROR_NONE; case DC_BTN_CMS: return ERROR_NONE; } // Incomplete Operation Processing ...
Run your calculator and try it out. The clear entry button and backspace allow you to edit the number being entered, and the clear button resets the calculator. Getting closer to a real calculator now. So now we have to deal with the “2nd” button and the memory operations. The memory operations are simple as they manipulate the global memory variable we made. Memory clear makes it zero, and memory retrieve replaces the “screen” with the value of the memory variable. Memory plus and subtract take the value of the memory and add or subtract the current “screen” value to it or from it, respectively. Finally, Memory store takes the “screen” value and saves it to the global memory.
int cal_action(int id, int action) { string tmp; // Control Processing ... case DC_BTN_C2: return ERROR_NONE; case DC_BTN_CMC: gbl_entering = false; gbl_memory = 0; return ERROR_NONE; case DC_BTN_CMR: gbl_entering = false; tmp = FormatString("%.11f", gbl_memory); tmp = MoneyRemoveTrailingZeros(tmp); EditSetText(DC_SCREEN, tmp); return ERROR_NONE; case DC_BTN_CMPLS: gbl_entering = false; gbl_memory = gbl_memory + DecimalToFloat(EditGetText(DC_SCREEN)); return ERROR_NONE; case DC_BTN_CMSUB: gbl_entering = false; gbl_memory = gbl_memory - DecimalToFloat(EditGetText(DC_SCREEN)); return ERROR_NONE; case DC_BTN_CMS: gbl_entering = false; gbl_memory = DecimalToFloat(EditGetText(DC_SCREEN)); return ERROR_NONE; } // Incomplete Operation Processing ...
All that remains is the “2nd” button. The logic for this button is simple. We change the value of our global second boolean. However, there is more code in the processor since we will change the look of the buttons for the user. We don’t have to change the buttons, but it would be very confusing if we didn’t. So the code is going to change the value of the boolean and then update the button labels. We also set this button to act as a checkbox so the user can see when it is active. We will use the CheckboxSetState function to accomplish that.
int cal_action(int id, int action) { string tmp; // Control Processing ... case DC_BTN_CCE: gbl_entering = false; EditSetText(DC_SCREEN, ""); return ERROR_NONE; case DC_BTN_C2: gbl_second = !gbl_second; if (!gbl_second) { EditSetText(DC_BTN_OSQ, LBL_SQ); EditSetText(DC_BTN_OPOW, LBL_POW); EditSetText(DC_BTN_OSIN, LBL_SIN); EditSetText(DC_BTN_OCOS, LBL_COS); EditSetText(DC_BTN_OTAN, LBL_TAN); EditSetText(DC_BTN_OSQRT, LBL_SQRT); EditSetText(DC_BTN_O10TO, LBL_10TO); EditSetText(DC_BTN_OLOG, LBL_LOG); CheckboxSetState(DC_BTN_C2, BST_UNCHECKED); } else { EditSetText(DC_BTN_OSQ, LBL_SQ_SEC); EditSetText(DC_BTN_OPOW, LBL_POW_SEC); EditSetText(DC_BTN_OSIN, LBL_SIN_SEC); EditSetText(DC_BTN_OCOS, LBL_COS_SEC); EditSetText(DC_BTN_OTAN, LBL_TAN_SEC); EditSetText(DC_BTN_OSQRT, LBL_SQRT_SEC); EditSetText(DC_BTN_O10TO, LBL_10TO_SEC); EditSetText(DC_BTN_OLOG, LBL_LOG_SEC); CheckboxSetState(DC_BTN_C2, BST_CHECKED); } return ERROR_NONE; case DC_BTN_CMC: gbl_entering = false; gbl_memory = 0; return ERROR_NONE; ...
If we run the calculator now, we have working memory and a “2nd” button that changes the look of the calculator. You should notice that our cal_run_operation function doesn’t reference the global second boolean. This means the buttons change but their behaviors do not. That’s not great. We need to go back to the cal_run_operation function and add in the secondary operations.
There are many ways to deal with the second processing, but I opted to just add an if statement to each of the operations that have secondary operations. So let’s add them in.
// // Math Processing // --------------- string cal_run_operation(float val) { int i1, i2; switch(gbl_operation) { case DC_BTN_OADD: gbl_operand = gbl_operand + val; break; case DC_BTN_OSUB: gbl_operand = gbl_operand - val; break; case DC_BTN_OMUL: gbl_operand = gbl_operand * val; break; case DC_BTN_ODIV: if (val == 0) { gbl_operand = 0; return ZERO_ERROR; } gbl_operand = gbl_operand / val; break; case DC_BTN_OSQ: if (gbl_second) { gbl_operand = gbl_operand * gbl_operand * gbl_operand; } else { gbl_operand = gbl_operand * gbl_operand; } break; case DC_BTN_OPOW: if (gbl_second) { if (val == 0) { gbl_operand = 0; return ZERO_ERROR; } gbl_operand = Power(gbl_operand, 1.0/val); } else { gbl_operand = Power(gbl_operand, val); } break; case DC_BTN_OSIN: if (gbl_second) { gbl_operand = ArcSine(gbl_operand); } else { gbl_operand = Sine(gbl_operand); } break; case DC_BTN_OCOS: if (gbl_second) { gbl_operand = ArcCosine(gbl_operand); } else { gbl_operand = Cosine(gbl_operand); } break; case DC_BTN_OTAN: if (gbl_second) { gbl_operand = ArcTangent(gbl_operand); } else { gbl_operand = Tangent(gbl_operand); } break; case DC_BTN_OSQRT: if (gbl_second) { if (gbl_operand == 0) { return ZERO_ERROR; } gbl_operand = 1 / gbl_operand; } else { gbl_operand = SquareRoot(gbl_operand); } break; case DC_BTN_O10TO: if (gbl_second) { gbl_operand = Power(2.71828, gbl_operand); } else { gbl_operand = Power(10, gbl_operand); } break; case DC_BTN_OLOG: if (gbl_second) { gbl_operand = Log(gbl_operand, 2.71828); } else { gbl_operand = Log(gbl_operand, 10); } break; case DC_BTN_OMOD: i1 = val; i2 = gbl_operand; gbl_operand = i2 % i1; break; } return ""; }
Easy as pie! Now we have even more functions added to the calculator. If you wanted to add more still, it would be as easy as defining another button and adding the code for it here. You would also need to add it to the operation processing in the action function depending on how many operands it has. Our calculator now has all of its operations coded using Legato for the math. While usable as it is, we can add some simple code to make it a little more user friendly. If you enter data using the number pad, when you hit the enter key the last entry is hit again. This is easily fixed in the action function by making all button actions set the keyboard focus.
int cal_action(int id, int action) { string tmp; // Always set focus back to equals if (action == BN_CLICKED) { ControlSetFocus(DC_BTN_OEQL); } ...
If you type a number and hit enter now nothing happens. What we really want is the enter key to run the equals function. If you notice we have a validate function that returns an error. This is because a dialog’s default behavior is to end with OK if the user hits enter. Our code as-is prevents the calculator from closing but it doesn’t run equals. We can run equals by running our action function manually.
int cal_validate() { cal_action(DC_BTN_OEQL, BN_CLICKED); return ERROR_SOFT; }
Now when you hit enter on the keyboard, the equals button is pressed. This means our calculator is also keyboard friendly. Pretty sweet.
Using Legato we have made an entire application that is user friendly. With the concepts learned here you can program a multitude of user interfaces. Happy coding!
Here’s the complete script:
// // Globals // ------- float gbl_operand; float gbl_memory; string gbl_last_val; boolean gbl_entering; boolean gbl_second; int gbl_operation; int gbl_last_operation; #define LBL_SQ "x ²" #define LBL_SQ_SEC "x ³" #define LBL_POW "x^y" #define LBL_POW_SEC "nthrt" #define LBL_SQRT "sqrt" #define LBL_SQRT_SEC "1/x" #define LBL_SIN "sin" #define LBL_SIN_SEC "asin" #define LBL_COS "cos" #define LBL_COS_SEC "acos" #define LBL_TAN "tan" #define LBL_TAN_SEC "atan" #define LBL_10TO "10^x" #define LBL_10TO_SEC "e^x" #define LBL_LOG "log" #define LBL_LOG_SEC "ln" #define ZERO_ERROR "Cannot divide by zero" // // Math Processing // --------------- string cal_run_operation(float val) { int i1, i2; switch(gbl_operation) { case DC_BTN_OADD: gbl_operand = gbl_operand + val; break; case DC_BTN_OSUB: gbl_operand = gbl_operand - val; break; case DC_BTN_OMUL: gbl_operand = gbl_operand * val; break; case DC_BTN_ODIV: if (val == 0) { gbl_operand = 0; return ZERO_ERROR; } gbl_operand = gbl_operand / val; break; case DC_BTN_OSQ: if (gbl_second) { gbl_operand = gbl_operand * gbl_operand * gbl_operand; } else { gbl_operand = gbl_operand * gbl_operand; } break; case DC_BTN_OPOW: if (gbl_second) { if (val == 0) { gbl_operand = 0; return ZERO_ERROR; } gbl_operand = Power(gbl_operand, 1.0/val); } else { gbl_operand = Power(gbl_operand, val); } break; case DC_BTN_OSIN: if (gbl_second) { gbl_operand = ArcSine(gbl_operand); } else { gbl_operand = Sine(gbl_operand); } break; case DC_BTN_OCOS: if (gbl_second) { gbl_operand = ArcCosine(gbl_operand); } else { gbl_operand = Cosine(gbl_operand); } break; case DC_BTN_OTAN: if (gbl_second) { gbl_operand = ArcTangent(gbl_operand); } else { gbl_operand = Tangent(gbl_operand); } break; case DC_BTN_OSQRT: if (gbl_second) { if (gbl_operand == 0) { return ZERO_ERROR; } gbl_operand = 1 / gbl_operand; } else { gbl_operand = SquareRoot(gbl_operand); } break; case DC_BTN_O10TO: if (gbl_second) { gbl_operand = Power(2.71828, gbl_operand); } else { gbl_operand = Power(10, gbl_operand); } break; case DC_BTN_OLOG: if (gbl_second) { gbl_operand = Log(gbl_operand, 2.71828); } else { gbl_operand = Log(gbl_operand, 10); } break; case DC_BTN_OMOD: i1 = val; i2 = gbl_operand; gbl_operand = i2 % i1; break; } return ""; } // // Supporting Dialog and Main Entry // -------------------------------- #define DC_BTN_N0 100 #define DC_BTN_N1 101 #define DC_BTN_N2 102 #define DC_BTN_N3 103 #define DC_BTN_N4 104 #define DC_BTN_N5 105 #define DC_BTN_N6 106 #define DC_BTN_N7 107 #define DC_BTN_N8 108 #define DC_BTN_N9 109 #define DC_BTN_NDEC 110 #define DC_BTN_NINV 111 #define DC_BTN_NPI 112 #define DC_BTN_OADD 113 #define DC_BTN_OSUB 114 #define DC_BTN_OMUL 115 #define DC_BTN_ODIV 116 #define DC_BTN_OEQL 117 #define DC_BTN_OSQ 118 #define DC_BTN_OPOW 119 #define DC_BTN_OSIN 120 #define DC_BTN_OCOS 121 #define DC_BTN_OTAN 122 #define DC_BTN_OSQRT 123 #define DC_BTN_O10TO 124 #define DC_BTN_OLOG 125 #define DC_BTN_OMOD 126 #define DC_BTN_CBACK 127 #define DC_BTN_C 128 #define DC_BTN_CCE 129 #define DC_BTN_C2 130 #define DC_BTN_CMC 131 #define DC_BTN_CMR 132 #define DC_BTN_CMPLS 133 #define DC_BTN_CMSUB 134 #define DC_BTN_CMS 135 #define DC_SCREEN 201 #define DC_OPERATION 202 int main() { gbl_operand = 0; gbl_memory = 0; gbl_last_val = ""; gbl_entering = false; gbl_second = false; gbl_operation = 0; gbl_last_operation = 0; DialogBox("CalculatorDlg", "cal_"); return 0; } int cal_load() { ControlChangeFont(DC_OPERATION, "Mono"); ControlChangeFont(DC_SCREEN, "Mono"); ControlChangeFont(DC_BTN_CBACK, "Wingding"); EditSetText(DC_SCREEN, "0"); return ERROR_NONE; } int cal_action(int id, int action) { string tmp; // Always set focus back to equals if (action == BN_CLICKED) { ControlSetFocus(DC_BTN_OEQL); } // Control Processing switch(id) { case DC_BTN_CBACK: if (gbl_entering == true) { tmp = EditGetText(DC_SCREEN); tmp = GetStringSegment(tmp, 0, GetStringLength(tmp) - 1); EditSetText(DC_SCREEN, tmp); } return ERROR_NONE; case DC_BTN_C: gbl_entering = false; gbl_operand = 0; gbl_operation = 0; EditSetText(DC_OPERATION, ""); EditSetText(DC_SCREEN, ""); return ERROR_NONE; case DC_BTN_CCE: gbl_entering = false; EditSetText(DC_SCREEN, ""); return ERROR_NONE; case DC_BTN_C2: gbl_second = !gbl_second; if (!gbl_second) { EditSetText(DC_BTN_OSQ, LBL_SQ); EditSetText(DC_BTN_OPOW, LBL_POW); EditSetText(DC_BTN_OSIN, LBL_SIN); EditSetText(DC_BTN_OCOS, LBL_COS); EditSetText(DC_BTN_OTAN, LBL_TAN); EditSetText(DC_BTN_OSQRT, LBL_SQRT); EditSetText(DC_BTN_O10TO, LBL_10TO); EditSetText(DC_BTN_OLOG, LBL_LOG); CheckboxSetState(DC_BTN_C2, BST_UNCHECKED); } else { EditSetText(DC_BTN_OSQ, LBL_SQ_SEC); EditSetText(DC_BTN_OPOW, LBL_POW_SEC); EditSetText(DC_BTN_OSIN, LBL_SIN_SEC); EditSetText(DC_BTN_OCOS, LBL_COS_SEC); EditSetText(DC_BTN_OTAN, LBL_TAN_SEC); EditSetText(DC_BTN_OSQRT, LBL_SQRT_SEC); EditSetText(DC_BTN_O10TO, LBL_10TO_SEC); EditSetText(DC_BTN_OLOG, LBL_LOG_SEC); CheckboxSetState(DC_BTN_C2, BST_CHECKED); } return ERROR_NONE; case DC_BTN_CMC: gbl_entering = false; gbl_memory = 0; return ERROR_NONE; case DC_BTN_CMR: gbl_entering = false; tmp = FormatString("%.11f", gbl_memory); tmp = MoneyRemoveTrailingZeros(tmp); EditSetText(DC_SCREEN, tmp); return ERROR_NONE; case DC_BTN_CMPLS: gbl_entering = false; gbl_memory = gbl_memory + DecimalToFloat(EditGetText(DC_SCREEN)); return ERROR_NONE; case DC_BTN_CMSUB: gbl_entering = false; gbl_memory = gbl_memory - DecimalToFloat(EditGetText(DC_SCREEN)); return ERROR_NONE; case DC_BTN_CMS: gbl_entering = false; gbl_memory = DecimalToFloat(EditGetText(DC_SCREEN)); return ERROR_NONE; } // Sequential Operation Processing if ((id != DC_BTN_OEQL) && (action == BN_CLICKED)) { if ((id > DC_BTN_NPI) && (gbl_operation != 0)) { cal_action(DC_BTN_OEQL, action); } } // Run Operation if (id == DC_BTN_OEQL) { if (gbl_operation == 0) { gbl_operation = gbl_last_operation; } else { gbl_last_val = EditGetText(DC_SCREEN); } tmp = cal_run_operation(DecimalToFloat(gbl_last_val)); gbl_last_operation = gbl_operation; gbl_operation = 0; if (tmp == "") { tmp = FormatString("%.11f", gbl_operand); tmp = MoneyRemoveTrailingZeros(tmp); } EditSetText(DC_SCREEN, tmp); EditSetText(DC_OPERATION, ""); } // Number entering switch(id) { case DC_BTN_N0: case DC_BTN_N1: case DC_BTN_N2: case DC_BTN_N3: case DC_BTN_N4: case DC_BTN_N5: case DC_BTN_N6: case DC_BTN_N7: case DC_BTN_N8: case DC_BTN_N9: if (gbl_entering == true) { tmp = EditGetText(DC_SCREEN); } tmp = tmp + FormatString("%d", id - DC_BTN_N0); EditSetText(DC_SCREEN, tmp); gbl_entering = true; break; case DC_BTN_NDEC: if (gbl_entering == true) { tmp = EditGetText(DC_SCREEN); } if (FindInString(tmp, ".") < 0) { tmp = tmp + "."; } EditSetText(DC_SCREEN, tmp); gbl_entering = true; break; case DC_BTN_NINV: tmp = EditGetText(DC_SCREEN); if (tmp != "") { if (tmp[0] == '-') { tmp = GetStringSegment(tmp, 1); } else { tmp = "-" + tmp; } EditSetText(DC_SCREEN, tmp); } gbl_entering = true; break; case DC_BTN_NPI: EditSetText(DC_SCREEN, "3.14159265358"); gbl_entering = false; break; default: if (action == BN_CLICKED) { gbl_entering = false; } break; } // Operation Processing switch(id) { case DC_BTN_OADD: case DC_BTN_OSUB: case DC_BTN_OMUL: case DC_BTN_ODIV: case DC_BTN_OPOW: case DC_BTN_OMOD: gbl_operand = DecimalToFloat(EditGetText(DC_SCREEN)); gbl_operation = id; tmp = EditGetText(id); if (FindInString(tmp, " ") == 0) { tmp = GetStringSegment(tmp, 4, 3); tmp = TrimString(tmp); } tmp = ReplaceInString(tmp, "x", ""); tmp = ReplaceInString(tmp, "&", ""); tmp = ReplaceInString(tmp, "y", ""); tmp = EditGetText(DC_SCREEN) + tmp; EditSetText(DC_OPERATION, tmp); EditSetText(DC_SCREEN, ""); break; case DC_BTN_OSQ: case DC_BTN_OSIN: case DC_BTN_OCOS: case DC_BTN_OTAN: case DC_BTN_OSQRT: case DC_BTN_OLOG: case DC_BTN_O10TO: gbl_operand = DecimalToFloat(EditGetText(DC_SCREEN)); gbl_operation = id; cal_action(DC_BTN_OEQL, action); break; } return ERROR_NONE; } int cal_validate() { cal_action(DC_BTN_OEQL, BN_CLICKED); return ERROR_SOFT; } #beginresource CalculatorDlg DIALOGEX 0, 0, 136, 193 EXSTYLE WS_EX_DLGMODALFRAME STYLE DS_MODALFRAME | DS_3DLOOK | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU CAPTION "Legato Calculator" FONT 8, "MS Shell Dlg" { CONTROL " 0 &0", DC_BTN_N0, "button", BS_PUSHBUTTON | BS_LEFT | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 55, 169, 25, 20, 0 CONTROL " 1 &1", DC_BTN_N1, "button", BS_PUSHBUTTON | BS_LEFT | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 30, 149, 25, 20, 0 CONTROL " 2 &2", DC_BTN_N2, "button", BS_PUSHBUTTON | BS_LEFT | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 55, 149, 25, 20, 0 CONTROL " 3 &3", DC_BTN_N3, "button", BS_PUSHBUTTON | BS_LEFT | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 80, 149, 25, 20, 0 CONTROL " 4 &4", DC_BTN_N4, "button", BS_PUSHBUTTON | BS_LEFT | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 30, 129, 25, 20, 0 CONTROL " 5 &5", DC_BTN_N5, "button", BS_PUSHBUTTON | BS_LEFT | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 55, 129, 25, 20, 0 CONTROL " 6 &6", DC_BTN_N6, "button", BS_PUSHBUTTON | BS_LEFT | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 80, 129, 25, 20, 0 CONTROL " 7 &7", DC_BTN_N7, "button", BS_PUSHBUTTON | BS_LEFT | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 30, 109, 25, 20, 0 CONTROL " 8 &8", DC_BTN_N8, "button", BS_PUSHBUTTON | BS_LEFT | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 55, 109, 25, 20, 0 CONTROL " 9 &9", DC_BTN_N9, "button", BS_PUSHBUTTON | BS_LEFT | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 80, 109, 25, 20, 0 CONTROL " ÷ &/", DC_BTN_ODIV, "button", BS_PUSHBUTTON | BS_LEFT | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 105, 89, 25, 20, 0 CONTROL " × &*", DC_BTN_OMUL, "button", BS_PUSHBUTTON | BS_LEFT | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 105, 109, 25, 20, 0 CONTROL " - &-", DC_BTN_OSUB, "button", BS_PUSHBUTTON | BS_LEFT | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 105, 129, 25, 20, 0 CONTROL " + &+", DC_BTN_OADD, "button", BS_PUSHBUTTON | BS_LEFT | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 105, 149, 25, 20, 0 CONTROL " = &=", DC_BTN_OEQL, "button", BS_PUSHBUTTON | BS_LEFT | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 105, 169, 25, 20, 0 CONTROL " . &.", DC_BTN_NDEC, "button", BS_PUSHBUTTON | BS_LEFT | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 80, 169, 25, 20, 0 CONTROL "±", DC_BTN_NINV, "button", BS_PUSHBUTTON | BS_CENTER | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 30, 169, 25, 20, 0 CONTROL "CE", DC_BTN_CCE, "button", BS_PUSHBUTTON | BS_CENTER | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 30, 89, 25, 20, 0 CONTROL "C", DC_BTN_C, "button", BS_PUSHBUTTON | BS_CENTER | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 55, 89, 25, 20, 0 CONTROL "\xd5", DC_BTN_CBACK, "button", BS_PUSHBUTTON | BS_CENTER | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 80, 89, 25, 20, 0 CONTROL LBL_SQ, DC_BTN_OSQ, "button", BS_PUSHBUTTON | BS_CENTER | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 5, 46, 25, 20, 0 CONTROL LBL_POW, DC_BTN_OPOW, "button", BS_PUSHBUTTON | BS_CENTER | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 30, 46, 25, 20, 0 CONTROL LBL_SIN, DC_BTN_OSIN, "button", BS_PUSHBUTTON | BS_CENTER | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 55, 46, 25, 20, 0 CONTROL LBL_COS, DC_BTN_OCOS, "button", BS_PUSHBUTTON | BS_CENTER | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 80, 46, 25, 20, 0 CONTROL LBL_TAN, DC_BTN_OTAN, "button", BS_PUSHBUTTON | BS_CENTER | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 105, 46, 25, 20, 0 CONTROL LBL_SQRT, DC_BTN_OSQRT, "button", BS_PUSHBUTTON | BS_CENTER | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 5, 66, 25, 20, 0 CONTROL LBL_10TO, DC_BTN_O10TO, "button", BS_PUSHBUTTON | BS_CENTER | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 30, 66, 25, 20, 0 CONTROL LBL_LOG, DC_BTN_OLOG, "button", BS_PUSHBUTTON | BS_CENTER | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 55, 66, 25, 20, 0 CONTROL "pi", DC_BTN_NPI, "button", BS_PUSHBUTTON | BS_CENTER | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 80, 66, 25, 20, 0 CONTROL "Mod", DC_BTN_OMOD, "button", BS_PUSHBUTTON | BS_CENTER | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 105, 66, 25, 20, 0 CONTROL "2nd", DC_BTN_C2, "button", BS_CHECKBOX | BS_PUSHLIKE | BS_CENTER | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 5, 89, 20, 13, 0 CONTROL "MC", DC_BTN_CMC, "button", BS_PUSHBUTTON | BS_CENTER | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 5, 124, 20, 13, 0 CONTROL "MR", DC_BTN_CMR, "button", BS_PUSHBUTTON | BS_CENTER | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 5, 137, 20, 13, 0 CONTROL "M+", DC_BTN_CMPLS, "button", BS_PUSHBUTTON | BS_CENTER | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 5, 150, 20, 13, 0 CONTROL "M-", DC_BTN_CMSUB, "button", BS_PUSHBUTTON | BS_CENTER | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 5, 163, 20, 13, 0 CONTROL "MS", DC_BTN_CMS, "button", BS_PUSHBUTTON | BS_CENTER | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 5, 176, 20, 13, 0 CONTROL "", DC_OPERATION, "edit", ES_RIGHT | ES_READONLY | WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP, 2, 4, 128, 10, 0 CONTROL "", DC_SCREEN, "edit", ES_RIGHT | ES_READONLY | WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP, 2, 16, 128, 20, 0 } #endresource
David Theis has been developing software for Windows operating systems for over fifteen years. He has a Bachelor of Sciences in Computer Science from the Rochester Institute of Technology and co-founded Novaworks in 2006. He is the Vice President of Development and is one of the primary developers of GoFiler, a financial reporting software package designed to create and file EDGAR XML, HTML, and XBRL documents to the U.S. Securities and Exchange Commission. |
Additional Resources
Legato Script Developers LinkedIn Group
Primer: An Introduction to Legato