Scripting LiteStep - The Works (updated for mzScript pre 1.0 and Textedit 2.4.14)
One of the areas that many people will encounter under LiteStep is scripting. Whether just a curious user trying to see what is going on under-the-hood or a top-notch themer trying to implement some new whizz-bang feature, you are likely to want to know what is possible and how it all actually works.
Scripting under LiteStep can be carried out in a number of ways - LiteStep itself has a very limited scripting ability that allows for conditional actions when LiteStep starts up and also provides for multiple command execution via the !execute command. This support, whilst useful, is usually not enough to really provide a complete solution to most needs.
The most complete scripting solution under LiteStep 0.24.6/0.24.7 is mzScript (currently at version pre-1.0) and over the next few months we'll show you how and why our scripting systems in LDE(X) work, using some initial discussions of the concepts of scripting and also of the limitations that you might want to be aware of.
Please note that whilst every effort has been made to provide an error-free and safe set of examples, we cannot and will not accept any responsibility for any data loss that may occur (along with any consequences that may have!) - each individual reader accepts such risks and liabilities at a personal level.
First things first.
Before we begin, you should really have the following :
LiteStep 0.24.7 (RC1 or later)
mzScript pre 1.0 (or later, assuming no syntax changes have been made)
textedit 2.4.14 (or later, assuming no syntax changes have been made - textedit 2.5 is bad due to a poor escape code design choice (\c))You may also wish to start with a dedicated LiteStep scripting folder, well away from any of your usual LiteStep stuff. That way you will be able to play in safety, none of your themes or data at risk.
You should be able to load a desktop module, popup module and hotkey module at a minimum, although LSXCommand would also be very useful. If you cannot manage this, you are getting ahead of yourself and need to get the LiteStep basics learnt first. It shouldn't take more than 1 hour to sort out most of it - you could even rip the code from a pre-made theme and use that. Just be sure that you have it all working before proceeding with anything here.
Backup this folder for restoration at any time - some of the later examples will show how to manipulate files and you will want to be able to restore the originals in case of problems.
Loading mzScript.
mzScript can be fussy about where and when it is loaded. Usually it is a good idea to load it last of all it can be more useful loaded threaded, although caution is advised (if you are going to use TextEdit with mzScript, run both threaded or unthreaded - not one threaded and one unthreaded) :
LoadModule "$LiteStepDir$Modules\Scripting\mzScript\mzScript.dll" threaded
All scripts can either be loaded via step.rc or by 'include' files (that you include in step.rc using include "$LiteStepDir$Includes\Scripts.rc"). You will need to recycle/restart LiteStep to load any new scripts you type in, although !reload or !refresh followed by !reloadmodule for mzScript may be perfectly legitimate routes depending on your scripts and requirements.
Your First Script.
Time to get your hands dirty - let's write our first script & in the tradition of things it will be :
*Script bang !helloworld
*Script exec !msgbox Hello World!
*Script ~bangIf you define a hotkey, popup menu entry or similar for !helloworld, you will be able to use this script. You can also type in !helloworld into LSXCommand if you have that loaded. Hereafter, we will simply refer to this as calling the function name.
If you typed things correctly up to now, you should see a message box appear on your screen with the Hello World! message being shown. If not, go back and check all your LoadModule and script code to ensure everything is correct.
So - what exactly does it all mean. Let's take a look at this script - the first line (*Script bang !helloworld) simply tells mzScript that you are defining a new bang command for the script that will be called !helloworld. The last line (*Script ~bang) marks the end of the script. Every line that has a *Script entry in front of it between these two aforementioned lines will then be used when !helloworld is called. In terms of scripting, these lines are important, but provide no real function in the scripts and so we will not consider them further - the first action in a script will be deemed to be the one in the line following *Script bang and similarly, the last action will be the penultimate line.
In this case, we only have one line (*Script exec !msgbox Hello World!) between the *Script bang and Script ~bang lines. The exec element is important and define the 'style' of the *Script entry being made - there are a number of them & you have already seen the bang style - we will come to the other styles very shortly. The exec style simply tells mzScript that you are going to perform a command (another LiteStep !bang command, or launch an application). In this situation, we are calling !msgbox (an internal feature of mzScript that provides alert boxes on screen - if you prefer, you can use the LiteStep ones such as !alert or !confirm) and asking that the message box shows Hello World!.
The Quality of Scripting can be, well, variable....
If you are reasonably familiar with LiteStep, this won't seem much to you - after all you could do the same thing with LiteStep itself. Let's try something slightly more interesting - this is the first leap and presents a number of fundamental concepts in one chunk. You are going to need three separate scripts for this one, two of them will be simply workarounds for deficiencies with the LiteStep !confirm command, but will also provide very useful later on. The third script will gradually evolve over the next few discussions :
*Script bang !yes
*Script exec !VarSet yes "1"
*Script ~bang*Script bang !no
*Script exec !VarSet no "1"
*Script ~bang*Script bang !areyouwell
*Script exec !confirm "Are you well?" !yes !no
*Script exec !IfExist "yes" [!msgbox Good - hope you stay that way]
*Script exec !IfExist "no" [!msgbox Oh dear - hope things improve]
*Script exec !VarRemove yes
*Script exec !VarRemove no
*Script ~bangTo use this correctly, call !areyouwell and respond appropriately.
To try and prevent scripts confusing LiteStep, mzScript will recognise (when appropriate) the '|' character as the '!' character. To that end, you can use *Script exec |about or *Script exec !about, but we'll talk about this a little later!) & all will work exactly the same way & you see that the !areyouwell script is using this to call !yes or !no using |yes or |no as applicable.
This is the first time we have encountered variables in mzScript & we will need to discuss them shortly. Just before that, a quick overview of what's going on in the rest of this example.
When you call !areyouwell, the first action (!confirm "Are you well?" !yes !no") generates a LiteStep-native message box with the question "Are you well?". If you click on the Yes button, the !yes function is called & the No button calls !no. For the syntax of the !confirm command, check the LiteStep documentation - it's an internal command.
The yes and no scripts are required because !confirm cannot handle spaces in its yes/no actions (i.e. This will not work : !confirm "are you well?" !VarSet yes "1" !VarSet no "1"). To understand what comes next a little better, a short preliminary discussion of variables is in order here (you can return here once you are happy).
Whenever you call one script from another, the script that is doing the call will stop until the called script is completed - this can be something that causes problems and you should remember this (i.e. !Script1 calls !Script2; !Script1 will stop at that point until !Script2 has completed - the same will be true of any further calls from !Script2 and so on).
The calls to !yes or !no allows us to create a variable called yes or no (as applicable) so that we can base the actions in the rest of the !areyouwell script on the user response to the !confirm dialog. We can then use the existence (or value) of these variables to determine what happens next. In this case, the value of the variable doesn't matter - it needs to have a value in order for the variable to be defined, but in this case we are only interested in which variable (yes or no) exists.
This can be seen in the two lines within !areyouwell :
*Script exec !IfExist "yes" [!msgbox Good - hope you stay that way]
*Script exec !IfExist "no" [!msgbox Oh dear - hope things improve]Each line will only be executed if the variable being queried (yes or no) exists. It should be fairly obvious what's be done here.
Finally, we have to clear up after ourselves - both to prevent problems with both yes and no potentially existing if the script is run a second time and also to keep the memory demands down. This can be seen with the two lines :
*Script exec !VarRemove yes
*Script exec !VarRemove noThe !VarRemove command is built into mzScript and always available. If you prefer, this can also be merged with the !IfExist check :
*Script exec !IfExist yes [!VarRemove yes]
*Script exec !IfExist no [!VarRemove no]In fact a further refinement should be available in :
*Script exec !IfExist yes [!VarRemove yes] [!VarRemove no]
All three are equally valid and function in the same way - an example of the flexibility there is. The encapsulator behaviour changed with mzScript pre1.0 so that only [] can be used - the previous '' and {} command encapsulators are no longer supported.
(In)Equality ..... not always a bad thing.
If you read the side-discussion on variables here, you will have noticed that we can actually also compare the values of variables in a number of ways. This can be very useful in trying to handle otherwise awkward situations. There are a number of 'operators' that you can use to try and ensure that your scripts work in exactly the way you want them to. Additionally, any kind of variables or construct can be used :
*Script exec !If ["%{myvariable}" = "12"] [!equal]
This is the basic way of doing things and might be the one you find yourself using the most. Note that both numbers and strings would work, but strings are better suited to being used with the alternate system !IfEq (we'll come to this in a minute).
The operators that are available are :
= : equal to
<> : not equal to
> : greater than
< : less thanYou are not confined to using mzScript variables or the construct as shown above either. You can also do any of the following without problems :
*Script exec !If ["%{myvariable}" = "$myEVar$"] [!equal]
*Script exec !If ["$myEVar$" = "hello"] [!equal]Consider our earlier script (we've left out the !yes and !no functions) :
*Script bang !areyouwell
*Script exec !confirm "Are you well?" !yes !no
*Script exec !IfExist "yes" [!msgbox Good - hope you stay that way]
*Script exec !IfExist "no" [!msgbox Oh dear - hope things improve]
*Script exec !VarRemove yes
*Script exec !VarRemove no
*Script ~bangWe can now change this to :
*Script bang !yes
*Script exec !VarSet yes "1"
*Script ~bang*Script bang !areyouwell-eval
*Script exec !confirm "Are you well?" {!yes} {!none}
*Script exec !If ["%{yes}" = "1"] [!msgbox Good - hope you stay that way] [!msgbox Oh dear - hope things improve]
*Script exec !IfExist "yes" [!VarRemove yes]
*Script ~bangThe obvious benefit is that there is one less bang command and one less variable required for this revision & it's actually slightly easier to understand what's going on now.
The !IfEq command might therefore seem somewhat irrelevant.
Ants in your pants.
Just like BASIC, mzScript allows for unconditional or conditional jumps to defined points in the current script being run - you cannot jump to a defined point in a separate function, however, but you could call that function with a variable check in place (as will be shown shortly) to workaround this problem. mzScript marks these points with labels. It's quite a simple system and taking the script from above, we can change the way it behaves with labels :
*Script bang !yes
*Script exec !VarSet yes "1"
*Script ~bang*Script bang !areyouwell-eval
*Script exec !confirm "Are you well?" !yes !none
*Script gotoif ["%{yes}" = "1"] well
*Script goto unwell
*Script label well
*Script exec !msgbox Good - hope you stay that way
*Script exec !IfExist "yes" [!VarRemove yes]
*Script goto end
*Script label unwell
*Script exec !msgbox Oh dear - hope things improve
*Script label end
*Script ~bangIn the script above, don't overlook the gotoif command - this works just like the !IfEq and !IfEval constructs we've already covered. Note also that there is no 'exec' with the use of goto or gotoif in the script above.
Note that one current limitation in mzScript is the absence of a !goto command for use in situations like :
*Script exec !IfExist myvariable [!goto mylabel]
For this kind of thing to work, you will need to use some trickery :
*Script gotoif ["%{myvariable} <> ""] mylabel
Infinite monkeys with typewriters.
Perhaps the most useful module to use with mzScript is TextEdit 2.x & whenever possible, use the latest version available. TextEdit 2 is the work of Tony Chang, with all revisions/perversions from leaf being marked with an 'l' tag to differentiate them.
TextEdit 2 is a module designed to manipulate content in a specified target file. The syntax (for the current version at the time of writing) is :
!textreplace @targetfilename@ @search string@ @replace string@
There are a number of commands available to you and also search strings; we'd suggest reading the documentation for the module (given that this is a scripting tutorial more than a textedit tutorial). The escape codes are extremely useful & perhaps the most useful way of working is to exploit the comment marker ';' to precisely target your action. Using the escape code for ';' that is '\~', the following example should illustrate the point :
*Script bang !textedittest
*Script exec !textreplace @$LiteStepDir$step.rc@ @(.*) \~MARKER$@ @new string \~MARKER@
*Script ~bangNote that many scripts that you will find pre-made don't use this approach and often instead use the full line of code from step.rc as the search string. Whilst this works OK, if you change that line of code you HAVE to update the script; if you have a complex stack of code, this isn't always remembered. Using ';MARKER's, this problem is alleviated & you can also use naming to make the whole thing much more descriptive. In step.rc, you might have PopupIcons "on" ;POPUPICONS and use a on and off script pair as shown below :
*Script bang !popupiconon
*Script exec !textreplace @$LiteStepDir$step.rc@ @^\~POPUPICON (.*)@ @\1 \~POPUPICON@
*Script ~bang*Script bang !popupiconoff
*Script exec !textreplace @$LiteStepDir$step.rc@ @(.*) \~POPUPICON$@ @\~POPUPICON \1@
*Script ~bangEssentially, this is the basis of our fish system; since this is a scripting tutorial, as we proceed, you should end up with your very own fish system at the end! Cool, eh?
Joining the X-Men
mzScript variables, just like those in BASIC, can be re-defined at any time and the definition can use the pre-existing defined value for the variable. This gives us some really useful possibilities that can be used to extend the scripts seen above. We gave this a term of variable mutation & it is the underpinning of the mechanisms in fish :
*Script bang !popupiconon
*Script exec !VarSet fishvar1 POPUPICON
*Script exec !on
*Script exec !VarSet fishvar1 %{fishvar1}ON
*Script exec !on
*Script exec !VarSet fishvar1 %{fishvar1}OFF
*Script exec !off
*Script ~bang*Script bang !popupiconoff
*Script exec !VarSet fishvar1 POPUPICON
*Script exec !off
*Script exec !VarSet fishvar1 %{fishvar1}ON
*Script exec !off
*Script exec !VarSet fishvar1 %{fishvar1}OFF
*Script exec !on
*Script ~bang*Script bang !on
*Script exec !textreplace @$LiteStepDir$step.rc@ @^\~%{fishvar1} (.*)@ @\1 \~%{fishvar1}@
*Script ~bang*Script bang !off
*Script exec !textreplace @$LiteStepDir$step.rc@ @(.*) \~%{fishvar1}$@ @\~%{fishvar1} \1@
*Script ~bangThis allows for 'swap over' handling, e.g.
*Popup "Turn off popup icons" !popupiconoff ;POPUPICONON
;POPUPICONOFF *Popup "Turn on popup icons" !popupicononThis may not seem immediately useful, but a slight revision should really begin to prove just what is possible.
Getting back to generics
First, we make a move to a slightly different structure and a more generic form :
*Script bang !popupiconon
*Script exec !VarSet fishvar1 POPUPICON
*Script exec !on
*Script ~bang*Script bang !popupiconoff
*Script exec !VarSet fishvar1 POPUPICON
*Script exec !off
*Script ~bang*Script bang !on
*Script exec !VarSet fishvar2 "@^\~%{fishvar1} (.*)@"
*Script exec !VarSet fishvar3 "@\1 \~%{fishvar1}@"
*Script exec !VarSet fishvar4 "%{fishvar2} %{fishvar3}"
*Script exec !doit
*Script exec !VarSet fishvar2 "@^\~%{fishvar1}ON (.*)@"
*Script exec !VarSet fishvar3 "@\1 \~%{fishvar1}ON@"
*Script exec !VarSet fishvar4 "%{fishvar2} %{fishvar3}"
*Script exec !doit
*Script exec !VarSet fishvar2 "@(.*) \~%{fishvar1}OFF$@"
*Script exec !VarSet fishvar3 "@\~%{fishvar1}OFF \1@ "
*Script exec !VarSet fishvar4 "%{fishvar2} %{fishvar3}"
*Script exec !doit
*Script ~bang*Script bang !off
*Script exec !VarSet fishvar2 "@(.*) \~%{fishvar1}$@"
*Script exec !VarSet fishvar3 "@\~%{fishvar1} \1@"
*Script exec !VarSet fishvar4 "%{fishvar2} %{fishvar3}"
*Script exec !doit
*Script exec !VarSet fishvar2 "@(.*) \~%{fishvar1}ON$@"
*Script exec !VarSet fishvar3 "@\~%{fishvar1}ON \1@"
*Script exec !VarSet fishvar4 "%{fishvar2} %{fishvar3}"
*Script exec !doit
*Script exec !VarSet fishvar2 "@^\~%{fishvar1}OFF (.*)@"
*Script exec !VarSet fishvar3 "@\1 \~%{fishvar1}OFF@"
*Script exec !VarSet fishvar4 "%{fishvar2} %{fishvar3}"
*Script exec !doit
*Script ~bang*Script bang !doit
*Script exec !textreplace @$LiteStepDir$step.rc@ %{fishvar4}
*Script ~bangThis might need a little explaining for both the choice of structure and also for the advantages this gives. It may also seem a little intimidating, but with a little thought you should see that the !on, !off and !doit functions are generic. That is, they simple handle variables (fishvar1 - fishvar4). The whole system is kicked off by the user calling one of the two specific functions (in this case, one of the two popupicon functions). In a slightly different way of working than before, all the mutations happen with the !on and !off functions. Finally, all the editing is performed by the !doit function.
The fishvar1 variable is set by the function called by the user - in this case, !popupiconon or !popupiconoff to simply hold POPUPICON. The control is then passed to !on or !off as required. The advantage to this is that the amount of scripting work you need to do to on implementing a new preference choice is simply :
*Script bang !popupiconon
*Script exec !VarSet fishvar1 POPUPICON
*Script exec !on
*Script ~bang*Script bang !popupiconoff
*Script exec !VarSet fishvar1 POPUPICON
*Script exec !off
*Script ~bangThere may be some who would argue that the use of fishvar2 and fishvar3 is actually superfluous - you could simply work with something like :
*Script exec !VarSet fishvar2 "@^\~%{fishvar1} (.*)@ @\1 \~%{fishvar1}@"
*Script exec !VarSet fishvar2 "@(.*) \~%{fishvar1}$@ @\~%{fishvar1} \1@"
This can be slightly harder to debug, but will reduce the amount of code if that's important.
Check out those threads.
mzScript 0.8l was compiled as a multithreaded module for use with the threading support found in LiteStep 0.24.6. Whilst this usually provides a significant performance advantage over the standard loading method, there are some important caveats to be aware of. To explain the problem, you first need to have taken note of these facts.
With threading enabled, control can be returned before you might expect & in some cases this might be extremely troublesome. To prevent ill effects, it is wise to take advantage of the execution system as discussed in the linked area of this document.
If you are using scripts that use TextEdit 2, such as !doit, you may want a safeguard in there. The problem is that, due to the slightly awkward threading execution order it can be possible for !VarRemove operations to remove a variable before TextEdit 2 gets to see it.
In the scripts above, we haven't implemented a variable cleanup system to remove fishvar1 through to fishvar4. We'll do this now, but there are certain issues you need to be aware of to avoid problems (and to prevent disasters if problems do occur!)
*Script bang !off
*Script exec !VarSet fishvar2 "@(.*) \~%{fishvar1}$@"
*Script exec !VarSet fishvar3 "@\~%{fishvar1} \1@"
*Script exec !VarSet fishvar4 "%{fishvar2} %{fishvar3}"
*Script exec !doit
*Script exec !VarSet fishvar2 "@(.*) \~%{fishvar1}ON$@"
*Script exec !VarSet fishvar3 "@\~%{fishvar1}ON \1@"
*Script exec !VarSet fishvar4 "%{fishvar2} %{fishvar3}"
*Script exec !doit
*Script exec !VarSet fishvar2 "@^\~%{fishvar1}OFF (.*)@"
*Script exec !VarSet fishvar3 "@\1 \~%{fishvar1}OFF@"
*Script exec !VarSet fishvar4 "%{fishvar2} %{fishvar3}"
*Script exec !doit
*Script exec !cleanup
*Script ~bang*Script bang !doit
*Script exec !textreplace @$LiteStepDir$step.rc@ %{fishvar4}
*Script ~bang*Script bang !cleanup
*Script exec !VarRemove fishvar1
*Script exec !VarRemove fishvar2
*Script exec !VarRemove fishvar3
*Script exec !VarRemove fishvar4
*Script ~bangThe reason for placing the call to !cleanup at the end of the !off function is to prevent the possibility of removing the variables when !doit is still using them.
Check, please.
An additional safeguard against threading issues is to use something like :
*Script bang !doit
*Script exec !If ["%{fishvar4}" <>""] [!textreplace @$LiteStepDir$step.rc@ %{fishvar4}]
*Script ~bangFinally, you may well want to consider adding a delay so that the writes to files are completed, especially if you are planning to recycle the LiteStep system after the changes have been made. When threading is being used, it is quite possible to recycle before all the changes have been made. Using the mzScript pause function for a delay of several seconds prior to using !recycle should be seriously considered.
Rentokil
The importance of debugging your code should never be overlooked; there are a number of routes available to you depending on what you are trying to do and how much code is involved.
For simple scripts, you can simply use the internal message box support built into both LiteStep and mzScript. These message boxes are invaluable because whilst they are displayed, the whole script stops. It only resumes when the message box is dismissed. One of the most useful options is to use the mzScript !VarShow function. It will show the existence status and value all within one box.
If you are using a more complex script, perhaps using multiple levels, you can use TextEdit 2 to write information out to a dedicated text file that you can read later. The simplest construct is to simply insert 'always on' debug code :
*Script bang !doit
*Script exec !textappend @debug.txt@ @doit called@
*Script exec !If ["%{fishvar4}" <>""] [!textreplace @$LiteStepDir$step.rc@ %{fishvar4}]
*Script exec !textappend @debug.txt@ @using fishvar4 of %{fishvar4}@
*Script exec !textappend @debug.txt@ @doit completed@
*Script ~bangThis is very ineffective due to the overhead of loading all the related code and trying to clean it up all of it later. There are two ways of making this much more elegant :
*Script exec !textappend @debug.txt@ @doit called@ ;DEBUG
This is the universal way of doing things and is probably the most efficient form. However, due to its universal nature, there can be some problems where multiple user considerations are in place. As such, EVars and the native If..Else..EndIf parsing within LiteStep can be used :
DEBUG "1" ;DEBUG
If DEBUG
*Script exec !textappend @debug.txt@ @doit called@
EndIfThe DEBUG declaration would go into a file for each individual user (perhaps in %userprofile%). If the DEBUG EVar is defined for the current user, the !textappend action is performed, otherwise it is not loaded.
If desired, a slight revision on this treatment can be made to use a mzScript variable. In this case, the code would look like :
*Script var DEBUG "1" ;DEBUG
*Script exec !IfExist DEBUG [!textappend @debug.txt@ @doit called@]
The advantage of this latter method is that the debug mode can be activated at any time without the need for recycling LiteStep.
Time to invest
mzScript provides a very useful load/save pair of features for variables. You can even tell mzScript to update the saved variable definitions following changes (including the ability to write out new, previously undefined variables to this file). This saves some of the effort involved in using variables because you don't need to remember to code for the saving of variable data, although it may not be entirely suitable if you are spreading variables across a number of files, rather than a single repository.
To get this to work, you need to define (unless you are planning to use the standard step.rc file) a 'repository' file containing the variables using mzScriptFile.
If you specify mzAutosaveVars (with a value of either replace or save), then mzScript will automatically write out the new values of variables with replace; the save option will enable this and also allow mzScript to write out newly defined variables that aren't present in the repository. The commands !VarSave and !VarSaveAll are available to save a specific variable (in the first case) or all variables (in the latter case) out to the defined file.
The 3 Rs
Well, we've covered a lot of material so far. We've handled reading and writing variables, along with various bits of trickery. However, we haven't covered the final 'R' - arithmetic. Yes, you can also perform a little numeric magic with variables.
You might ask why this would be necessary - it's not as though you are building a calculator. That much is true, but you might want to track the number of times an operation has been performed (so you can close down the appropriate number of dialogs or simply to post a warning that opening more than one of something is not a good idea....). To do this is a very simply exercise - the increment / decrement code snippets below should give you some idea.
*Script bang !increment
*Script exec !IfExist greenbottle [] [!VarSet greenbottle 0]
*Script exec !msgbox "greenbottle is: %{greenbottle}"
*Script exec !VarAdd greenbottle 1
*Script exec !msgbox "greenbottle is: %{greenbottle}"
*Script ~bang*Script bang !decrement
*Script exec !VarAdd greenbottle -1
*Script exec !msgbox "greenbottle is: %{greenbottle}"
*Script ~bangIn the first case, note that we define the variable if it doesn't exist already - arithmetic operands in mzScript will not create a non-defined variable and will therefore fail in this situation unless you define the variable yourself.
In the second case, we are simply using a negative number to decrement the value. Where this might be more useful is in a situation such as creating or destroying instances of displayed components as shown in the next section.
Note that you can also use variables such as shown below, it's perfectly valid.
*Script exec !VarAdd greenbottle %{greenbottle}
In a similar manner, you can also use multiplications :
*Script exec !VarMul greenbottle 2
*Script exec !VarMul greenbottle %{greenbottle}Note that since string variables are treated as having a zero value, multiplication with such variables will zero your target variable and cause all kinds of trouble (unless you want this, but then using 0 would be a better idea!)
The Art of Conversation
Something that is useful is to use the variables system with ckDialog & leaf developed Converse to do just that. The use of this kind of tracking of instances of each kind of dialog allows you to ensure that multiple instances are handled correctly - a problem in the standard ckDialog way of doing things as it doesn't allow wildcards for closing down all instances of the same dialog.
You can see the scripts below; the documentation for converse will elaborate on the details. Essentially, Converse is just a slightly more sophisticated version of the variable handling code you've seen up until now. Remapping variables within !initdialogvar and !enddialogvar for use in !opendialog and !closedialog works around the absence of support for recursive variables within mzScript and other than that, it's all fairly straightforward.
*Script bang !opendialog
If DEBUG
*Script exec !textappend @debug\converse-debug.txt@ @opendialog called.@
EndIf
*Script exec !initdialogvar
*Script gotoif ["%{dialogshown}" = "%{dialoglimit}"] exit
*Script exec !VarAdd dialogshown 1
If DEBUG
*Script exec !textappend @debug\converse-debug.txt@ @dialogname is %{dialogname} and instance is %{dialogshown}.@
EndIf
*Script exec !ckCreateDialog %{dialogname}Dialog %{dialogname}%{dialogshown}
*Script label exit
*Script exec !enddialogvar
*Script exec !VarRemove dialoglimit
If DEBUG
*Script exec !textappend @debug\converse-debug.txt@ @opendialog completed.@
EndIf
*Script ~bang
*Script bang !closedialog
If DEBUG
*Script exec !textappend @debug\converse-debug.txt@ @closedialog called.@
EndIf
*Script exec !initdialogvar
*Script label closeinit
If DEBUG
*Script exec !textappend @debug\converse-debug.txt@ @dialogname is %{dialogname} and instance is %{dialogshown}.@
EndIf
*Script exec !ckaction "%{dialogname}%{dialogshown}.show" "close"
*Script exec !VarAdd dialogshown -1
*Script gotoif ["%{closesingle}" = "1"] closeend
*Script gotoif ["%{dialogshown}" > "0"] closeinit
*Script label closeend
If DEBUG
*Script exec !If ["%{closesingle}" = "1"] [!textappend @debug\converse-debug.txt@ @Close loop terminated at instance %{dialogshown} of %{dialogname}.@] [!textappend @debug\converse-debug.txt@ @All instances of %{dialogname} terminated.@]
EndIf
*Script exec !VarRemove closesingle
*Script exec !enddialogvar
If DEBUG
*Script exec !textappend @debug\converse-debug.txt@ @closedialog completed.@
EndIf
*Script ~bang*Script bang !initdialogvar
If DEBUG
*Script exec !textappend @debug\converse-debug.txt@ @initdialogvar called.@
EndIf
*Script exec !If ["%{dialogname}" = "APPAB"] [!VarSet dialogshown %{APPABShown}]
*Script exec !If ["%{dialogname}" = "APPAB"] [!VarSet dialoglimit "1"]
*Script exec !If ["%{dialogname}" = "LBXAB"] [!VarSet dialogshown %{LBXABShown}]
*Script exec !If ("%{dialogname}" = "LBXAB"] [!VarSet dialoglimit "1"]
*Script exec !If ["%{dialogname}" = "Paths"] [!VarSet dialogshown %{PathsShown}]
*Script exec !If ["%{dialogname}" = "Paths"] [!VarSet dialoglimit "1"]
*Script exec !If ["%{dialogname}" = "About"] [!VarSet dialogshown %{AboutShown}]
*Script exec !If ["%{dialogname}" = "fish"] [!VarSet dialogshown %{fishShown}]
*Script exec !If ["%{dialogname}" = "Slowdown"] [!VarSet dialogshown %{SlowdownShown}]
*Script exec !If ["%{dialogname}" = "Warning"] [!VarSet dialogshown %{WarningShown}]
*Script exec !If ["%{dialogname}" = "WarningUnload"] [!VarSet dialogshown %{WarningUnloadShown}]
*Script exec !If ["%{dialogname}" = "Feature"] [!VarSet dialogshown %{FeatureShown}]
*Script exec !If ["%{dialogname}" = "Deprecated"] [!VarSet dialogshown %{DeprecatedShown}]
*Script exec !If ["%{dialogname}" = "UserFiles"] [!VarSet dialogshown %{UserFilesShown}]
*Script exec !IfExist dialogshown [] [!textappend @debug\converse-debug.txt@ @%{dialogname} not indexed - check \^initdialogvar in converse-scripts.rc\^ (Attempting to continue with forced instance of 0)@]
*Script exec !IfExist dialogshown [] [!VarSet dialogshown 0]
If DEBUG
*Script exec !textappend @debug\converse-debug.txt@ @initdialogvar completed.@
EndIf
*Script ~bang*Script bang !enddialogvar
If DEBUG
*Script exec !textappend @debug\converse-debug.txt@ @enddialogvar called.@
EndIf
*Script exec !If ["%{dialogname}" = "APPAB"] [!VarSet APPABShown %{dialogshown}]
*Script exec !If ["%{dialogname}" = "LBXAB"] [!VarSet LBXABShown %{dialogshown}]
*Script exec !If ["%{dialogname}" = "Paths"] [!VarSet PathsShown %{dialogshown}]
*Script exec !If ["%{dialogname}" = "About"] [!VarSet AboutShown %{dialogshown}]
*Script exec !If ["%{dialogname}" = "fish"] [!VarSet fishShown %{dialogshown}]
*Script exec !If ["%{dialogname}" = "Slowdown"] [!VarSet SlowdownShown %{dialogshown}]
*Script exec !If ["%{dialogname}" = "Warning"] [!VarSet WarningShown %{dialogshown}]
*Script exec !If ["%{dialogname}" = "WarningUnload"] [!VarSet WarningUnloadShown %{dialogshown}]
*Script exec !If ["%{dialogname}" = "Feature"] [!VarSet FeatureShown %{dialogshown}]
*Script exec !If ["%{dialogname}" = "Deprecated"] [!VarSet DeprecatedShown %{dialogshown}]
*Script exec !If ["%{dialogname}" = "UserFiles"] [!VarSet UserFilesShown %{dialogshown}]
*Script exec !VarRemove dialogshown
*Script exec !VarRemove dialogname
If DEBUG
*Script exec !textappend @debug\converse-debug.txt@ @enddialogvar completed.@
EndIf
*Script ~bang
You can see that here a loop in !closedialog between closeinit and closeend is being exploited to ensure that every (tracked) dialog instance that has been recorded in dialognameshown is closed down (!initdialogvar and !enddialogvar serve to map the instances to and from dialogshown and %{dialogname}shown). With each work through the loop a dialog is closed and the tracking variable has its value decremented by 1 until the value of dialognameshown is zero. At this point, all dialog instances are closed and the script can exit. Note that !closedialog also supports closing a single dialog instance if the appropriate closesingle variable has been set prior to calling !closedialog - occasionally useful.
It's Good To Talk
One thing that is often useful in terms of LiteStep scripting is to be able to directly interact with your users. By asking them questions, you can make the script more useful to them and provide the services they are looking for. The most primitive way of doing this is to use the internal capabilities of LiteStep (!alert for example). We're not even going to touch that aspect of things - if you've got this far and that's all you want, the documentation exists and should present no problems. We're going to continue on in search of a touch of class.
Really determined and clued up users of LiteStep will go straight for ckDialog. This module is a little complicated & it's beyond the scope of this document to help you out - an example straight from ckDialog's author is provided below. Unless you have LiteSpeak loaded and the icon as defined, this will not work straight out of the box.
; LiteSpeakDialog
*ckDialog new
*ckDialog id LiteSpeakDialog
*ckDialog icon "c:\\LiteStep\\themes\\whistler\\images\\litestep.ico"
*ckDialog title "LiteSpeak Dialog"
*ckDialog size 300 300 300 50
*ckDialog style "wsclipchildren | wsoverlappedwindow"
*ckDialog edit
*ckDialog id speakTextEdit
*ckDialog size 0 2 235 44
*ckDialog xstyle "wsxclientedge"
*ckDialog ~edit
*ckDialog button
*ckDialog id speakButton
*ckDialog title "Speak"
*ckDialog size 240 0 52 23
*ckDialog ~button
*ckDialog ~new*onckDialogEvent LiteSpeakDialog.speakButton.click "!ckExecEvaluate !Speak %[LiteSpeakDialog.speakTextEdit.title]"
The last line of the definition above is where things get a little tricky and you must understand what is happening in order for it all to work as you want. To explain this better, consider the following :
!ckCreateDialog LiteSpeakDialog LiteSpeakDialog
This action is documented in the ckDialog package and the second field above relates to the definition of the dialog as seen above (definition ID). The third field gives the name of the instance of the dialog (instance ID) and is the way that you can identify instances of dialogs that are otherwise the same - for example :
!ckCreateDialog LiteSpeakDialog LiteSpeakDialog2
creates a second, otherwise identical dialog to the one we just made, but it has a different instance ID to identify it. ckDialog does allow you (unfortunately) to make many instances with the same instance ID for the same definition ID - unhelpful. You might think this is not a problem, but the problem becomes evident when you are dealing with the button action(s), as shown in the line :
*onckDialogEvent LiteSpeakDialog.speakButton.click "!ckExecEvaluate !Speak %[LiteSpeakDialog.speakTextEdit.title]"
The code within the %[ ] encapsulation refers to the dialog instance ID. If you don't get the target right within the %[ ], ckDialog returns garbage and this is also true if the instance doesn't exist. Unhelpful. You might then think, hey we can use variables like in the dialog creation/destruction discussion above. This is possible with mzScript code might that looks like this (using !initdialogvar from above to get dialogshown to be set correctly):
*Script exec !ckExecEvaluate !Speak %[LiteSpeakDialog%{dialogshown}.speakTextEdit.title]"
(This won't work with pre-0.8.xl versions of mzScript, however, because mzScript expects to evaluate anything contained in %[ ] and will fail badly.)
You can't do the evaluation in the ckDialog code because it cannot read mzScript variables, only set them up for you. You do need to set some code in the dialog itself to set variables for mzScript to work with, naturally, and this is where a lot of trouble and frustration can be saved if you look at the line below and use that. Don't be tempted to link several actions together with a single button in ckDialog-side code. It rarely works well and generally you are better off using mzScript or multiple buttons.
*onckDialogEvent MyDialog.MyButton.click "!ckexecevaluate !VarSet mymzvariable %[MyDialogInstance.MyTextEdit.title]"
mzScript variables are not available (easily) to modules like ckDialog directly so trying to use the tracking variable within the *onckDialogEvent code would not work. Currently, the only workable solution is to limit the dialogs to single instances with a known (static) instance ID when you want to use the input / output support to get user input and output from a dialog. The process is relatively simple, initially, as you just throw buttons and text edit / list boxes around like there was no tomorrow. Just reference all code to the instance name which, for a single dialog, can be the same as the definition ID.
Out of Order
Occasionally, it is possible to run into a slightly strange situation that arises from the way that the parser in LiteStep handles itself. If you are using a myriad of 'include'd files it is possible to encounter a situation where a script is loaded, but the EVar(s) used by one or more scripts haven't been defined yet. mzScript then issues an error message that the EVar(s) don't exist and the script doesn't work. In some situations, this can be avoided by simply re-working the load order of your include file statements. In other situations, this isn't possible and so a slightly more inventive route needs to be taken.
The simplest route is to use a little trick to exploit the absence of recursive variable handling in mzScript and use variables to contain the EVar encapulator character ($) and allow mzScript to do the rest. Simply, just use code that looks like this for any problem areas of code.
*Script exec !VarSet EVarWorkAround "$"
*Script exec !mycommand %{EVarWorkAround}evarname%{EVarWorkAround}
*Script exec !VarRemove EVarWorkAroundThat's all there is to it.
Variables in LiteStep are in fact variable.....
You can return to the main discussion using this link.
LiteStep has variables that are defined at start-up and these are called EVars. You will have seen and used them, perhaps without realising, using the $notation$ (e.g. $LiteStepDir$). These variables have their definition locked for the current LiteStep session - you cannot change them and nor can you create or remove them without restarting LiteStep. They are essentially hard-coded and static.
mzScript introduces a different type of variable, completely unrelated to the EVars previously mentioned. They are not as addressable as EVars and you use a different syntax for them (%{variablename}). Like LiteStep, mzScript provides a number of variables as soon as you load it (try calling !msgbox %{xresolution} or !msgbox %{hour}:%{minute}:%{second}), but you also create, remove and manipulate your own at any time - mzScript has a fully dynamic variable system.
Anyone who's previously used BASIC or similar should soon see the parallels :
You set a variable using !VarSet variablename "definition" (e.g. !VarSet ldeisgreat "i agree")
You remove a variable using !VarRemove variablename (e.g. !VarRemove ldeisgreat)
You can re-define a variable by using !VarSet and can also use variable definitions with this (e.g. !VarSet ldeisgreat "%{ldeisgreat} wholeheartedly")
You can check is a variable exists using the mzScript !IfExist command (e.g. !IfExist ldeisgreat [!msgbox "It exists!"] or doesn't exist using !IfExist ldeisgreat [][!msgbox "boo"].
You can check for values, etc. and inter-variable agreement using the !If and !IfEq commands (e.g. !If ["%{myvariable}" = "12"] [!equal] or !If ["$myEVar$" = "12"] [!about] or if not using !If ["%{myvariable} = "$myEVar$"] [][!about]).
mzScript also supports a limited form of manipulation of numeric variables. Addition (!VarAdd) and multiplication (!VarMul) are both available with string variables being treated as numeric variables with the value of '0'. Note that these operations will not work when used with a variable that has not been defined yet - you must create the variable with !VarSet first. Additionally, if you use a variable defined with a string, the value it takes in the equation is zero. It would also be a silly operation to perform on a string variable in any case.