how to use strings as associative array keys? - associative-array

I am writing a simple GUI in AutoHotkey, one of the elements is the ability to associate a number with a button label:
Call:
{
book := {"Tel Maison": 8912, "Tel Mobile": 000000}
nr := book[%A_GuiControl%]
MsgBox %A_GuiControl% - number: %nr%
}
CapsLock::
Gui, Add, Button, gCall, Tel Maison
Gui, Add, Button, gCall, Tel Mobile
Gui, Show
When running this script I get, upon pressing a button, an error message (The following variable name contains an illegal caracter: "Tel Maison") pointing to
nr := book[%A_GuiControl%]
I believe that this is due to the space character in the label name.
Q1: isn't it possible to use hash keys with a space?
I modified the script to
Call:
{
book := {"TelMaison": 8912, "TelMobile": 000000}
nr := book[%A_GuiControl%]
MsgBox %A_GuiControl% - number: %nr%
}
CapsLock::
Gui, Add, Button, gCall, TelMaison
Gui, Add, Button, gCall, TelMobile
Gui, Show
It now runs but the variable nr is empty.
Q2: The label is passed via A_GuiControl as a string, right? If so why isn't there a match for the key in the example above?
I also tried to use book := {TelMaison: 8912, TelMobile: 000000} but the result is the same

This is a typical example of expressions vs. string literals in AHK, which can be troublesome in some cases. In order to use variable contents as a key for an object, simply put the variable in the brackets and don't enclose them in percentage signs (%):
nr := book[A_GuiControl]
This way, you can very well use spaces for the button names.
What's the problem with book[%A_GuiControl%]?
AHK expects either a hard-coded string (e.g. book["Tel Maison"]) or a variable when you access an object property by key. Since we don't pass it a hard string, it's assuming that TelMaison or Tel Maison is the variable name. Accessing Tel Maison will directly lead to a runtime error, since variable names can't have spaces. TelMaison is legal, but contains nothing. So basically, we access the object with an empty string as the key (like book[""]). This, by the way, isn't illegal and you could in fact use the empty string as a key, although I think that's not reasonable in most cases.
Why does AHK offer this weird syntax then?
There are scenarios in which you might want to use the contents of a variable in turn as a variable name. Look at this example for instance:
TelMaison := 8912
TelMobile := 0
Gui, Add, Button, gCall, TelMaison
Gui, Add, Button, gCall, TelMobile
Gui, Show
Exit
Call:
nr := %A_GuiControl%
msgbox % nr
return
With nr := %A_GuiControl%, nr is assigned the value of the variable, whose name is equal to the contents of A_GuiControl. I wouldn't recommend this pattern though, since it's very error-prone and simply bad design.

Related

How can I dynamically change the where conditions of a for each loop?

I have a table of records that has two logical flags, holdn and holdl. I want to loop through this table with 3 different criteria.
Either flag is TRUE - We want to see everything that is on hold
Flag holdl is TRUE - We only want see items that are on hold for this one reason
Flag holdn is TRUE - We only want to see items that are on hold for this other reason.
I cannot figure out how to dynamically change the for each loop based on this. What I have tried so far is to set the value of a variable based on these conditions and then use the content of the variable as one of the where parameters. This does not work as Progress complains that there is a data mismatch. The variable is a string, the flags are logical, so that does make sense. See sample code below. This is a snippet of the actual code with the the table name changed. The which-hold, order-from, etc variables are defined and set in a different module which calls this one.
DEFINE VARIABLE which-hold# AS CHARACTER FORMAT "x(30)" NO-UNDO.
CASE which-hold:
WHEN "B" THEN which-hold# = "(widget.holdn or widget.holdl)".
WHEN "L" THEN which-hold# = "widget.holdl".
WHEN "N" THEN which-hold# = "widget.holdn".
END CASE.
for each widget where which-hold# and
widget.order-no >= order-from and widget.order-no <= order-thru and
widget.line-no >= line-from and widget.line-no <= line-thru and
widget.joint-no >= joint-from and widget.joint-no <= joint-thru
no-lock:
A bunch of code to make a nice report with the retrieved records...
end.
Self taught Progress programmer here, who has inherited a huge, poorly documented application. Please be gentle.
If you would prefer not to deal with handles a semi-dynamic approach is also possible:
define variable i as integer no-undo.
define query q for customer.
do while true:
update i.
case i:
when 0 then quit.
when 1 then open query q for each customer no-lock where custNum >= 1000.
when 2 then open query q for each customer no-lock where state = "nh".
otherwise open query q for each customer no-lock where name begins "u".
end.
do while true with frame a:
get next q.
if not available customer then leave.
display custNum name state with frame a 10 down.
down with frame a.
end.
close query q.
end.
What you want is actually a dynamic query. I'll get to it at the end, but first I'd like to explain why you won't be able to try and substitute the field name in the which-hold# variable: because the query is evaluated at compile time. And this is what it reads (supposing which-hold# has a value of widget.holdn
FOR EACH widget where "widget-holdn" (...)
And that does not evaluate to TRUE or FALSE. So what, you ask? Well, that is the key here. Every condition needs to evaluate to true or false, so you'd be more in luck if you try
for each widget where (if widget-hold# = 'widget.holdn' then widget.holdn = true else TRUE) (...)
Again, notice the condition will exist if widget-hold# has the value I want, otherwise it doesn't filter on this at all.
So you can just code the way I showed (for each of the conditions you have) and it should work fine.
BUT let me suggest a dynamic query instead.
You need to have:
DEFINE VARIABLE hQuery AS HANDLE NO-UNDO.
CREATE QUERY hQuery.
hQuery:SET-BUFFERS(BUFFER widget:HANDLE).
hQuery:QUERY-PREPARE('<THIS IS THE CORE>').
hQuery:QUERY-OPEN().
DO WHILE hQuery:GET-NEXT():
A bunch of code to make a nice report with the retrieved records...
END.
So in the core you have a string that corresponds to your for each the way you want it to look. So it should be for example (store this in a variable, or assemble it inside the query prepare, it doesn't matter):
'FOR EACH widget NO-LOCK WHERE ' +
(if which-hold = 'B' then 'widget.holdn = true and widget.holdl = true'
else if which-hold = 'L' then 'widget-holdl = true'
else /* N */ 'widget-holdn = true').
Remember I said your query is evaluated at compile time? Well, just so you know, dynamic queries on the other end are evaluated at run time, so be prepared for errors to pop up only when you run. Another thing I should mention is dynamic queries are slower than static ones, so please evaluate and choose your poison :)
This should be what you need. Please let me know if it's helpful or any questions remain.

QT setInputMask() : how to interpolate string with user input in QLineEdit

I want the user to enter the input in QLineEdit in the following format
Array {99, 99, 99, 99}
where
1) Array { } is already present in the lineEdit. User only enters comma separated integers.
2) The no. of integers entered may vary.
I tried using setInputMask() for task (1) (backslashes to escape characters)
setInputMask("\A\r\r\a\y\{99\, 99\, 99\}");
But this does not work. Please help.
Disclaimer: This is a partial answer.
For your task (1), you should use double back-slashes to escape them. From the documentation (and in your case), only the following need to be escaped: A, a, {, }.
setInputMask("\\Arr\\ay\\{99\\,99\\,99\\}");
Otherwise, the compiler would (should) warn you about warning: unknown escape sequence: '\S'
With task (2), an idea would be to subclass QLineEdit, and dynamically update the inputMask() at every keypress by overloading keyPressEvent(). Another idea would be to set an input mask with, say, a hundred 99 or x's then to use a regex to validate user input.

Define global variables

I'm trying to test some algorithms in LibreOffice Calc and I would like to have some global variables visible in all cell/sheets. I searched the Internet and all the posts I have seen are so cryptic and verbose!
What are some simple instructions of how can I do that?
Go to Sheet → Named Ranges and Expressions → Define. Set name to "MyVar1" and expression to 5. Or for strings, use quotes as in "foo". Then press Add.
Now enter =MyVar1 * 2 in a cell.
One strategy is to save the global variables you need on a sheet:
Select the cell you want to reference in a calculation and type a variable name into the 'Name Box' in the top left where it normally says the Cell Column Row.
Elsewhere in your project you can reference the variable name from the previous step:
Using user-defined functions should be the most flexible solution to define constants. In the following, I assume the current Calc spreadsheet file is named test1.ods. Replace it with the real file name in the following steps:
In Calc, open menu Tools → Macros → Organize Macros → LibreOffice Basic:
At the left, select the current document test1.ods, and click New...:
Click OK (Module1 is OK).
Now, the Basic IDE should appear:
Below End Sub, enter the following BASIC code:
Function Var1()
Var1 = "foo"
End Function
Function Var2()
Var2 = 42
End Function
The IDE should look as follows:
[![Enter image description here][5]][5]
Hit Ctrl + S to save.
This way, you've defined two global constants (to be precise: two custom functions that return a constant value). Now, we will use them in your spreadsheet. Switch to the LibreOffice Calc's main window with file test1.ods, select an empty cell, and enter the following formula:
=Var1()
LibreOffice will display the return value of your custom Var1() formula, a simple string. If your constant is a number, you can use it for calculations. Select another empty cell, and enter:
=Var2() * 2
LibreOffice will display the result 84.

How to replace a string pattern with different strings quickly?

For example, I have many HTML tabs to style, they use different classes, and will have different backgrounds. Background images files have names corresponding to class names.
The way I found to do it is yank:
.tab.home {
background: ...home.jpg...
}
then paste, then :s/home/about.
This is to be repeated for a few times. I found that & can be used to repeat last substitute, but only for the same target string. What is the quickest way to repeat a substitute with different target string?
Alternatively, probably there are more efficient ways to do such a thing?
I had a quick play with some vim macro magic and came up with the following idea... I apologise for the length. I thought it best to explain the steps..
First, place the text block you want to repeat into a register (I picked register z), so with the cursor at the beginning of the .tab line I pressed "z3Y (select reg z and yank 3 lines).
Then I entered the series of VIM commands I wanted into the buffer as )"zp:.,%s/home/. (Just press i and type the commands)
This translate to;
) go the end of the current '{}' block,
"zp paste a copy of the text in register z,
.,%s/home/ which has two tricks.
The .,% ensures the substitution applies to everything from the start of the .tab to the end of the closing }, and,
The command is incomplete (ie, does not have a at the end), so vim will prompt me to complete the command.
Note that while %s/// will perform a substitution across every line of the file, it is important to realise that % is an alias for range 1,$. Using 1,% as a range, causes the % to be used as the 'jump to matching parenthesis' operator, resulting in a range from the current line to the end of the % match. (which in this example, is the closing brace in the block)
Then, after placing the cursor on the ) at the beginning of the line, I typed "qy$ which means yank all characters to the end of the line into register q.
This is important, because simply yanking the line with Y will include a carriage return in the register, and will cause the macro to fail.
I then executed the content of register q with #q and I was prompted to complete the s/home/ on the command line.
After typing the replacement text and pressing enter, the pasted block (from register z) appeared in the buffer with the substitutions already applied.
At this point you can repeat the last #qby simple typing ##. You don't even need to move the cursor down to the end of the block because the ) at the start of the macro does that for you.
This effectively reduces the process of yanking the original text, inserting it, and executing two manual replace commands into a simple ##.
You can safely delete the macro string from your edit buffer when done.
This is incredibly vim-ish, and might waste a bit of time getting it right, but it could save you even more when you do.
Vim macro's might be the trick you are looking for.
From the manual, I found :s//new-replacement. Seemed to be too much typing.
Looking for a better answer.

^[[A character combination

On Unix, when I press up arrow key, it shows this string, but while scanf, it does not take it as input. Please explain how to take it as input. Can we something like compare the character by charater like first ^[ is Esc key and so on?
That's the escape sequence generated by that key. '^[' is CTRL-[ (the ESC character), and the other two characters are '[' and 'A'.
If you want to process them, you'll need to read all three characters and decide that they mean the user pressed the up-arrow key.
Whether or not you can do this with your scanf depends on the format string. I would be using a lower level of character input for this.
I never use [f]scanf in real code since failure results in you not knowing where the input pointer is located. For line-based input, I find it's always better to use fgets and then sscanf the string retrieved.
But, as I said, you should be using getc and its brethren for low-level character I/O. Or find a higher level function such as readline under Linux, or other libraries that know to convert it into special keycodes such as VK_KEY_UP that you can process.

Resources