Autohotkey

Constructive Language Criticism

& Script Merging

 

 

Written by Miodrag Milic

miodrag.milic@gmail.com

www.r-moth.com

 

 

Download examples used in this article

 

 

Table of Contents

 

 

Constructive Language Criticism.. 1

Labels. 1

WhiteSpace. 2

Variable Scope. 3

Strings. 4

Commands with the same meaning. 4

 

Multithreading & script merging. 4

Names. 5

Includes and autorun section. 6

Multiple GUIs. 7

Tray menu. 9

Message Handlers. 10

The final part – adding the flag. 14

WHY do wee need all this. 16

Running included scripts. 17

 

Some Thoughts About The Future. 18

 

 

 

This article is written for experienced users of Autohotkey and for the author of this powerful open-source scripting language. I will try to underscore some problematic issues in the current language environment and to influence future developing plans with some, by my experience, useful ideas and propositions.

 

I will also write about some methodologies I am using in my personal developing, while discussing issues with AHK. I don’t want to recommend this as programming reference or how things should be done - this is just what I find appropriate for achieving some things that are not obvious in regular AHK usage.

 

Constructive Language Criticism

 

Labels

I find implementation of labels in AHK different from almost any other I encounter.

The problem is in the fact that labels are poorly context aware, that is, they can be set at almost any location. You can jump out of function, inside a function and up to day, I am uncertain where can I put them. For instance, you can’t jump into the loop, or at the end of the function but I constantly discover some new ways to jump, many of them are suggested by AHK users on the official forum.

 

Look at this example of handling windows message WM_KEY.

 

OnMessage(WM_KEY, “Dispatch”) ;jump inside a function

return

 

MessageHandler(wparam,lparam)

{

     Goto functionCall

 Dispatch:

Lparam := Application_lparam

wparam := Application_wparam

         

      functionCall:

; do some stuff here   

}

 

WhiteSpace

Whitespace influence the code execution.

 

MyFunction()

{ global ;will never be executed

  ...

}

The one generally can’t organize code the way he/she wants because whitespace influence compilation. global in the above code will never be executed because of the { in the same line, and error can be easily overseen. This is consequence of string literals witch don’t have to be quoted by design and absence of command terminator:

   

(1) s = some text

(2) s := “some text"

       

Both sentences are legal in AHK. Mentioned reasons don’t allow us to write code like:

 

s := “some text”; Send %s%; return;

 

I used ; here as command separator since it is often used in this role in other languages (it is a comment character in AHK)

I propose changes here so the whitespace doesn’t influence the program flow.

 

We lose:     Easy variable assignment like (1) and syntax

 

              s = %s% new text %p%

                witch would have to be done only using “.” Operator, this way:

              s := s . “new text” . p

                witch I consider as trivial difference.

 

We get:      - Better code organization and so, better handling and maintaince.

                - One way to assign variables instead 2, which means less errors.

 

 

Variable Scope

global & local keywords can be really confusing at the beginning. With practice this fades away but still some things are not obvious.

There is also that thing about arrays. Assume that you want to access global and local arrays inside a function:

 

MyFunction()

{

     (1) global              (2) local

     (1) local i, j          (2) global globalArray_*

 

     ... 

     globalArray_%i% := “something global”

     localArray_%j% := “something local”

}

 

If you use (1) you will have access to the globalArray, but localArray will be also created global.

If you use (2) you will have to explicitly name all globalArray items, witch can’t be done for large arrays.

So there is no way to have both global and local arrays in the same function.

 

You can construct wrapper like this:

 

MyFunction()

{

     local

     ... 

     SetGlobal(globalArray_%i%, “something global”)

     localArray_%j% := “something local”

}

 

SetGlobal(ByRef var, value)

{

global

     var := value

}

 

but that is far from convenient

 

Solution:

        Adding metacharacter for arrays, like * so (1) can be written as

 

(1) global   

     (1) local i, j, localArray_*

 

The meaning of “*” should be understood as:

        All variables with the prefix equal to word before * will be treated as local/global

         

Another very important thing about scope is that we have only 2 of them.

 

Unit scope should be also considered or even namespace to achieve script merging possible.

I will talk about this later when discussing script merging.

 

Strings

This is another thing that obviously needs improvement in AHK. You can’t access individual string characters because there are no arrays in AHK. So you can’t write something like this:

 

 s := “Some long string with configuration details”

 if (s[20]=”F” && s[43]=”3”)

     ...

Now you must extract each string position with StringMid function or use &, possibly create some wrapper around it but that is no wise solution for reasons that will become apparent latter in the text, when script merging is discussed.

 

Solution:

Adding array reference operator, like [] or any other convenient. This can be done internally or via preprocessor that will translate [] calls to appropriate function calls, like StringMid. We lose [] in naming variables but we get convenient mechanism for array manipulation.

 

 

Commands with the same meaning

I guess compatibility is the only reason why AHK have 2 commands doing the same thing on almost every place. This is confusing, error prone, and not effective at all, since function parameters are expressions while their alternatives use literals. Those commands that don’t have function alternatives are harder to use, since they can not be placed in expressions so separate code lines and variables must be created. Then, if you want those variables to be local thus to avoid global variable cloud fields you have to make sure you are doing it in a function, because subroutines can’t have local variables. Documentation fog is another problem.

 

My suggestion is to mark non-function commands obsolete and to keep function versions (and to create function alternatives for commands that don’t have them).

 

I am aware that this is a major compatibility issue, but compatibility sometimes must be broken for the sake of the greater good. Appropriate directive can be added to new scripts to make AHK recognize them as new (and command line parameter also), thus to invoke older compiler scheme when running obsolete scripts. Thus we can have both types of scripts on transition.

 

We get:

        Sophisticated code, capable of better constructs.

        Fewer errors

        Less variables and code lines.

 

We lose:

        We would have to specify additional directive at the start of every script, like #AHK_2

This will happen only in transition, since later, this can be set as default script environment, similar to what we will have with #NoEnv directive.

 

 

Multithreading & script merging

 

AHK can not have multiple threads of execution.  I find this to be the major problem, since without it you can’t run multiple, by all other means standalone scripts, by merging them into the single script.

 

Although this is true in general, script merging can be facilitated in situations when scripts don’t run in parallel because scripts will not interrupt each other. This is usually the case with the hotkeys.

 

The reasons for merging scripts are various, and most of them will become apparent at the end of this discussion.

 

Things that need to be taken into account are:

 

-         Names (GUIs, menus, labels, function names, and global variables)

-         Includes and autorun sections

-         Tray menu

-         WinAPI message handlers

 

 

The primer that follows observes 2 scripts that I created, FavMenu and Editor.

 

FavMenu is Open/Save standard dialog enhancer that will create hotkey for you to easily change the path within a dialog without the need to browse to the destination and Editor is its menu editor. Those scripts are the proof of the concept for the technique that I will discuss in the next section.

 

Both scripts are complex, with more then 1000 lines each, but FavMenu is separated into several ahk scripts to keep developing easier and to keep GUIs as separate units. FavMenu has 2 Guis, named Properties & Setup, while Editor has single GUI that is launched on startup. FavMenu can be merged into a single script without includes at the end.

 

Names

Technique starts by defining naming scheme to simulate OO environment by using “_” instead of typical “.”

All functions, labels, global variables and menu names are constructed using script’s base name:

    

FavMenu_myFunction

FavMenu_myLabel

Editor_globalVar

        ...

 

This will solve the issue of name collision when merging, since scripts can be created to have unique prefix.

FavMenu GUIs are in separate scripts so I didn’t use FavMenu prefix for them but their own names ( Setup_,  Properties_ )

This is done to shorten the length of elements inside the script and to keep focus on OO design but the final merged script will have 3 prefixes instead the one: FavMenu_, Properties_ and Setup_

 

The best practice is to keep number of global variables as small as possible since they can be named without prefix at all witch leads to code that is easier to read. This is problematic with subroutines witch don’t have local variables so wrapper MUST be created for each subroutine that can’t be avoided, like those for menus:

 

 

FavMenu_SetTrayMenu()

{

     Menu, tray, add, Setup,      FavMenu_trayDispatch

     Menu, tray, add, Disable,    FavMenu_trayDispatch

     Menu, tray, add

     Menu, tray, add, About,      FavMenu_trayDispatch

     Menu, tray, add, Reload,     FavMenu_trayDispatch

     Menu, tray, add, Exit,      FavMenu_trayDispatch

}

;------------------------------------------------------------

FavMenu_TrayHandler()

{

     global

     local msg

     ...

}

 

FavMenu_trayDispatch:

   FavMenu_TrayHandler()

Return

 

To quickly convert subroutine to function do the following:

-         replace : with ()

-         add new line and write {

-         replace return with }

-         place global keyword as the  first function command

-         place Dispatch wrapper bellow.

-   make sure none of the function’s variables are global except when really needed.

 

 

There is no need to translate subroutines that don’t need local variables like:

 

FavMenu_MenuHotKey:

     FavMenu_CreateFullMenu()

     FavMenu_Show()

     FavMenu_Destroy()

Return

 

Includes and autorun section

Since all includes are literally pasted into specified position via #include directive  it is the best to put them at the end of the mother script.

However, there might be things inside the child script that should be executed in the autorun section of the parent script.

 

The rule here is simple:

Do as little as you must in autorun section. Use Init function instead.

 

All commands once in the autorun section, move into the initializer function that should be named SCRIPT_Init().

Furthermore, some things have to be obeyed inside initializer body:

 

 

#NoEnv

#SingleInstance force                 (0)

 

FAVMENU_Init()

Return

 

FAVMENU_Init()

{

     global

    

DetectHiddenWindows, on           (1)

     SetKeyDelay, -1

 

     #include messages.ahk             (2)

     #include tc_cmnds.ahk

 

     Favmenu_title      = TC FavMenu  (3)

     Favmenu_version     = 1.0b8

     Favmenu_configFile = Config.ini

     ...

}

 

By using this approach, Autorun section will be executed only when script is run as standalone, that is, if FavMenu.ahk is not used within some mother script. When merging, its autorun section will be placed somewhere in the middle of the mother script and will never be executed.

 

Mother script then should call Init functions of all merged scripts to correctly setup their working environments:

 

Mother script

 

FAVMENU_Init()

     EDITOR_Init()

Return

 

     ...

     #include FavMenu

     #include Editor

         

At the end, script can be run as standalone or within mother script with this simple mechanism.       

 

Sections (0) – (3) exist to solve some problems with directives .

 

(0)            Place here all directives that will influence standalone run but are not appropriate while calling inside other scripts. I typically use here #NoEnv & #SingleInstance. Those directives may change behavior of other scripts so it is the best to keep them private.

(1)            Place here all directives that should be executed no matter the calling context.

(2)            Since FavMenu is consisting of multiple scripts I put things here that should be run in autorun section.

(3)            Initialize your global variables here.

 

Multiple GUIs

Problem here is in the strange syntax of the GUI command. GUIs don’t have names, but are referenced as numbers. This is obviously a problem in merging if 2 scripts use the same gui number. GUI numbers will also depend on the sequence of Init functions in the mother script.

                                                      

The first thing that has to be done is to avoid using numbers in GUI definitions but variables. I standardized this variable in my scripts to be named as Window_GUI. Furthermore, I place GUI declaration in Window_Create function:

 

Content of GUI_Setup.ahk:

 

Setup_Create()

{

     global

    

     Gui, %Setup_GUI%:-sysmenu

     Gui, %Setup_GUI%:Add, Text, x75 y10 w156 h17, Total Commander Exe && Ini

     Gui, %Setup_GUI%:Add, Edit, vSetup_eTcExe x23 y32 w260 h20

     ...

     }

 

Then, add 1 parameter to the Init function:

 

FavMenu.ahk:

 

FAVMENU_Init( guiStart=0 )

{

     global

 

     DetectHiddenWindows, on

     SetKeyDelay, -1

 

     ...

     ;set guis

     Properties_GUI := guiStart + 1

     Setup_GUI      := guiStart + 2

 

     return guiStart + 2

}

 

Using this technique, mother script can call FavMenu & Editor using this approach:

       

     g := FAVMENU_Init(g)

     g := EDITOR_Init(g)

 

When FavMenu script is called standalone its guiStart parameter will default to 1, since in its own autorun section there is only

one function call - call to the Init without parameters - so everything will work correctly.

 

The problem here emerges with the use of GUI labels, like NGuiClose. Labels can not be created dynamically. So there is no way to set a label like

 

     %Setup_GUI%GuiClose:

              ...

     return

 

The only solution to this problem is to set those events via the message monitors. This will produce more problems as different scripts

will monitor the same window message with different handlers. The scheme for this will be presented latter in the text.

 

After next section I will describe the most problematic aspect in AHK script merging – message handlers.

The thing to remember here - this technique will be extended to handle other problems - is that recognition of script context (is it executed within another script or directly) lay in the parameters of the Init function.

 

Tray menu

The problem: If script A set the tray menu, then script B does so, menu will become merged and all except the last item with the same name will be lost. There are several solutions to this, I am describing 2 of them and I am using second one in my example.

 

If you want final Tray menu to contain all items from all scripts you are merging you have to check if items with the same name already exist or otherwise you will see only the last one added. AHK doesn’t have menu item identifiers and they are distinguished only by name. So we must check if another script has already added the item we want to add and if that is the case, to change the name of our item, by adding invisible characters as item suffix, thus making its name appear the same but having different binary interpretation. Invisible character is so called pseudo-space, symbol ALT 0160. This is script that adds two items with the same name:

 

Menu, myMenu, UseErrorLevel, on

Menu, myMenu, add, item1, MenuHandler

Menu, myMenu, rename, item1, item1

if (!ErrorLevel)

     menu myMenu,add,item1 ,MenuHandler

 

Note the pseudo-space after “item1” and before “,” in the last command. In real situations the script should check what is the first available sufix to set since there may be several items with the same name. For each successive item with the same name script should add additional pseudo-space.

 

Another approach is more elegant and without so much additional code: Init function should receive another argument, witch is the name of the Tray submenu in witch to place scripts own menu. If this argument is empty, script should add its menu items directly, if not it should add its menu items in the Tray submenu supplied.

 

FAVMENU_Init( guiStart=1, subMenu=”” )

{

     Global

     ...

     FavMenu_SetTrayMenu( subMenu )

}

 

FavMenu_SetTrayMenu( subMenu )

{

     menu := subMenu

     if subMenu =

     {

          ;set standalone environement

          menu := “Tray”

          Menu, %menu%, icon, shell32.dll, 44

          Menu, %menu%, NoStandard

          Menu, %menu%, tip, TC Fav Menu

     }

 

     Menu, %menu%, add, ...

}

Using this approach, when script is run standalone it will create its own tray menu with its own icon. If run within another script it will create its items under the submenu witch should be set by the mother script since it is responsible for the Tray:

 

g := FAVMENU_Init(g, "Application_FavMenu")

EDITOR_Init(g, “Application_Editor”)

 

Menu, Tray, Add, FavMenu, :Application_FavMenu

Menu, Tray, Add, Editor,  :Application_Editor

Menu, Tray, Icon, ...

return

 

#include FavMenu.ahk

#include Editor.ahk

 

 

Message Handlers

The problem: If both script A and script B set the message handler for the same windows message, the handler of the first one will be deleted.

 

In my script-merging technique this situation presents serious problem.

introducing GUI names had a side-effect of replacing GUI events with appropriate message events.

 

Every GUI must monitor WM_SYSCOMMAND to catch ALT F4 to be able to replace NGuiClose event.

Furthermore, I missed OnShow event, witch is currently not available in AHK, so I introduced custom (user) windows message witch I call WM_AHKSHOW, that will be sent to the window itself after all controls are created and before calling Gui, Show.

 

Let me show you how I handle this in the FavMenu.

FavMenu consist of 2 GUIs, Properties and Setup. When Favmenu is displayed, the one can edit menu item properties via CTRL ENTER shortcut in the Properties GUI. This window will allow you to edit item’s title, command and icon. Setup GUI is used to create FavMenu environment, that is, all things needed for application to run normally: location of the menu file, location of the file manager, desired hotkeys and so on... This is typical scenario, almost any serious script will have some interface for this, and at least 2 GUIs.

 

Since Favmenu is large script, I divided it into many separate scripts, most important I encapsulated both Guis in separate files called GUI_Properties.ahk and GUI_Setup.ahk.

 

This is how Properties GUI is created.

 

Properties_Create()

{

      global

 

      Gui, %Properties_GUI%:Add, Text,    x16  y5   w80  h16      vProperties_sTitle,                                    Title

      Gui, %Properties_GUI%:Add, Edit,    x16   y22  w200 h18    vProperties_eTitle

 

      ...

      ...

 

Gui, %Properties_GUI%:Add, Text,    x16  y102 w80  h16,                                                                                               Icon

      Gui, %Properties_GUI%:Add, Picture, x46  y102 w12  h12      vProperties_picIcon BackgroundTrans AltSubmit

      Gui, %Properties_GUI%:Add, Edit,    x17   y119 w248 h20     vProperties_eIcon,

      Gui, %Properties_GUI%:Add, Button,  x267 y121 w18  h17      gProperties_OnBrowseClickDispatch,                    ..

      Gui, %Properties_GUI%:Add, Button,  x16  y155 w104 h19      gProperties_OnSaveClickDispatch,                      &Save

     

      Gui, %Properties_GUI%:+LastFound

      Properties_hwnd := WinExist() + 0

           

      ;call start event

      DllCall("SendMessage", "uint", Properties_hwnd, "uint", WM_AHKSHOW, "uint", 0, "uint", 0)

 

      ;show the window

      Gui, %Properties_GUI%:Show, x370 y313 h185 w299, Properties

      ...

}

 

%Properties_GUI% is set via FavMenus Init function (described in Multiple Guis section).

Then I take its handle using combination of WinExist and LastFound option. Add 0 at the end to force its decimal format instead hex.

 

Before showing the window, I am sending the message to it, to let it know that it is about to be displayed.

I use this event later to set starting values for the controls in the GUI.

Setup_GUI is created exactly the same way.

 

Connecting messages to GUIs is done in messages.ahk & msg_dispatch.ahk

 

Content of messages.ahk

 

WM_SYSCOMMAND     = 0x112

WM_KEYDOWN        = 0x100

WM_APP            = 0x8000

WM_AHKSHOW        := WM_APP + 0x100

SC_CLOSE          = 0xF060   

 

OnMessage( WM_AHKSHOW,        "FavMenu_MessageMonitor")

OnMessage( WM_SYSCOMMAND,     "FavMenu_MessageMonitor")

OnMessage( WM_KEYDOWN,        "FavMenu_MessageMonitor")

 

Since above commands need to be executed in the autorun section of the FavMenu witch is redirected to the Favmenu_Init(..) function, I include above file in this function.

 

FAVMENU_Init( ... )

{

      global

      DetectHiddenWindows, on

      SetKeyDelay, -1

 

      #include includes\messages.ahk

      ...

 

This will enable message handlers on the startup of the FavMenu.

 

Now, since all messages are received by the single function, FavMenu_MessageMonitor witch resides in msg_dispatch.ahk script, I needed a mechanism to transmit the messages to appropriate GUIs.

 

That is, if Properties GUI received a message, or its control, call its message handler, or do the same for the Setup GUI. This is the code that does this:

 

FavMenu_MessageMonitor(wparam, lparam, msg, hwnd)

{

      global Properties_hwnd, Setup_hwnd

      h := DllCall("GetParent", "uint", hwnd)

      if h = 0

            h := hwnd

 

      if (h = Properties_hwnd)

             Properties_Monitor(wparam, lparam, msg)

      if (h = Setup_hwnd)

             Setup_Monitor(wparam, lparam, msg)

}

;-----------------------------------------------------------------------------------

Properties_Monitor(wparam, lparam, msg)

{

      global

      if (msg = WM_KEYDOWN)

            return Properties_OnKeyDown(wparam, lparam)

      if (msg = WM_AHKSHOW)

            return Properties_Show()

      if (msg = WM_SYSCOMMAND and wparam = SC_CLOSE)

            return Properties_Close()

}

;-----------------------------------------------------------------------------------

Setup_Monitor(wparam, lparam, msg)

{

global

      if (msg = WM_KEYDOWN)

            return Setup_OnKeyDown(wparam, lparam)

      if (msg = WM_AHKSHOW)

            return Setup_Show()

      if (msg = WM_SYSCOMMAND and wparam = SC_CLOSE)

            return Setup_Close()

}

 

This kind of setup will make us create black boxes for each Gui. Controls are set on GUI_Show() event, windows are destroyed or hidden in GUI_Close() event.

 

The same is done in the other standalone script, Editor.ahk, but directly without includes.

EDITOR_Init( ... )

{

      global

      OnMessage(0x0112,  "Editor_MessageHandler")     ; WM_SYSCOMMAND

      OnMessage(0x0100,  "Editor_MessageHandler")     ; WM_KEYDOWN

      ...

 

Now, both scripts will act fine when called standalone, but, when merged, the latest included script will erase message handlers (if set) for all previous GUIs. Very bad.

 

OnMessage( msgNum, handler ) AHK command when called without second parameter will return current message handler for the message number at the first parameter. You might think that we can save this function in the variable to be able to use it this way:

 

      oldMessageHandler := OnMessage(WM_KEYDOWN)

      OnMessage(WM_KEYDOWN, OnKeyDown)

      ...

 

      OnKeyDown(wparam, lparam)

      {

            ; do my stuf here

           

; at the end, call original handler

%oldMessageHandler%(wparam, lparam)

      }

Unfortunately, this isn’t possible in AHK in the current version.

Fortunately, we can use subroutines that can be called from a variable via GoSub %label% command.

 

I will introduce a new function here that I will use to achieve above code:

 

Application_AddMessageHandler (msg, label)

            Msg     - Message to monitor

            Label – Subroutine that will receive the message. Parameters will be transmited via global variables

                          Application_mMsg,   Application_mHwnd,    Application_mWparam,   Application_mLparam

 

 

 

Content of the file _Application.ahk

 

Application_AddMessageHandler(msg, label)

{

      global

     

      if !Application_aFlag_%label%

      {

            Application_hCount += 1

            Application_aMsgHandlers_%Application_hCount% := label

            Application_aFlag_%label% := true

      }

 

      OnMessage(msg, "Application_MessageMonitor")

}

 

;-----------------------------------------------------------------------

 

Application_MessageMonitor(wparam, lparam, msg, hwnd)

{

  global

  local label

 

  Application_mMsg      := msg

  Application_mHwnd     := hwnd

  Application_mWparam   := wparam

  Application_mLparam   := lparam

 

  Loop, %Application_hCount%

  {

        GoSub % Application_aMsgHandlers_%A_Index%

  }

}

 

 

With this, we will replace OnMessage(..) calls with Application_AddMessageHandler calls.

Every script should include _Application.ahk at the end of its inserts. In merged environment this will include this script only once. Furthermore, child scripts must have only one message monitor that will dispatch messages to its own windows. It is important to save the handles of the Guis to be able to recognize if one particular received message is for your GUI or some other in the merged script. That’s why I use this kind of code in the Editor and FavMenu:

 

Editor_MessageHandler(...)

global Editor_hwnd

      h := DllCall("GetParent", "uint", hwnd)

      if h = 0

            h := hwnd + 0

     

      if (h != Editor_hwnd)

            return

      ...

 

So what we have here are two different situations:

 

1. If the child script is run standalone, Application will be just another dispatcher and will dispatch all messages to the script’s actual message handler.

 

2. Child scripts that run at the same time in the merged environment will all have their messages dispatched witch was the point of this construction.

 

 

The final part – adding the flag

In order to distinguish exactly if the script is run standalone or inside other script, another and the last parameter to the Init(…) function must be added.

 

EDITOR_Init( lastGUI=0, subMenu="", bStandalone=true )

{

      Application_AddMessageHandler( 0x0112,  "Editor_MessageHandlerDispatch")     

      Application_AddMessageHandler( 0x0100,  "Editor_MessageHandlerDispatch")

 

      Editor_GUI := lastGui + 1

      Editor_standalone := bStandalone

      ...

      ...

      return lastGui + 1

}

Now we are able to omit some interface details that are not needed in merged environment.

For instance, if Editor is run standalone it will exit application on ALT F4. But when running as a part of a group of scripts, it will choose to destroy its GUI and free the resources occupied by internal arrays used for the interface elements:

 

Editor_Close()

{

      global

 

      if (Editor_Standalone)

            Editor_Exit()

      else

      {

            Editor_mnuCnt := 0

            Gui %Editor_GUI%:Destroy

 

            ;clean up arrays here

      }

}

 

FavMenu chooses to skip Exit and Reload items in the tray menu and not to set the tray icon.

 

      if (Favmenu_standalone)

      {

            FavMenu_trayMenu := "Tray"

            Menu, %FavMenu_trayMenu%, icon, shell32.dll, 44

            Menu, %FavMenu_trayMenu%, NoStandard

            Menu, %FavMenu_trayMenu%, Tip, FavMenu

      }

 

      Menu, %FavMenu_trayMenu%, add, Setup,     FavMenu_trayDispatch

      Menu, %FavMenu_trayMenu%, add, Disable,   FavMenu_trayDispatch

      Menu, %FavMenu_trayMenu%, add

      Menu, %FavMenu_trayMenu%, add, About,     FavMenu_trayDispatch

 

      if (Favmenu_standalone)

      {

            Menu, %FavMenu_trayMenu%, add, Reload,    FavMenu_trayDispatch

            Menu, %FavMenu_trayMenu%, add, Exit,      FavMenu_trayDispatch

     }


WHY do wee need all this

 

We did both scripts to support mergining. Now its time to merge them.

Look at this beauty:

 

 

Content of PARENT.ahk:

 

      g := 1

 

      g := FAVMENU_Init(g, "TCFavMenu", false)

      g := EDITOR_Init(g, "Editor", false)

 

      SetTrayMenu()

return

;----------------------------------------------------------------------------

+p::

      Editor_Run()

return

 

+e::

      ExitApp

return

;----------------------------------------------------------------------------

trayHandler:

      if (A_ThisMenuItem = "Exit")

            ExitApp

     

      if (A_ThisMenuItem = "Editor")

            Editor_Run()

 

if (A_ThisMenuItem = "Show FavMenu")

      {

            t := Favmenu_Options_MenuPos

            Favmenu_Options_MenuPos := 1        ; set mouse position

            FavMenu_Create()

            Favmenu_Options_MenuPos := t        ; restore old position

      }

return

;----------------------------------------------------------------------------

SetTrayMenu()

{

       Menu, Tray, nostandard

       Menu, Tray, Icon, shell32.dll, 42

       Menu, Tray, Add, FavMenu, :FavMenu

       Menu, Tray, Add,

       Menu, Tray, Add, Show FavMenu, trayHandler

       Menu, Tray, Add, Editor, trayHandler

       Menu, Tray, Add,

       Menu, Tray, Add, Exit, trayHandler

}

;----------------------------------------------------------------------------

#include Favmenu.ahk

#include Editor.ahk

#include _APPLICATION.ahk

 

The Parent is exactly 38 lines long.

The difference is that only one AutoHotKey instance is running, not 2. On every next standalone script this will become more and more pronounced.

 

Another beauty of this system is that the Parent here is actually acting as a plug-in container and child scripts as plugins. Parent can create its own interface for merged environment, it can even enable/disable plugins. Furthermore, nothing limits the level of depth in merging so you can create packs that will be included in other more general packs and so on…

 

Imagine: one system utilities pack, one keyboard remapper pack and one general pack all united in single parent that can control them all through “published” interface of its children. By “published interface” I mean about scripts functions and options that are to be exposed to be used in scripts control. That will depend on desired level of interaction. In this model, we have this available for parent:

 

     EDITOR_Init(...)   ;must

     FAVMENU_Init(...)  ;must

 

     FavMenu_Create()             ;show the favmenu

      Favmenu_Options_MenuPos      ;menu position: 1 = mouse, 2 = cursor, 3 = center

 

     Editor_Run()       ;run the editor

 

 

The only note is that the script can not run anything continuous since it will be interrupted by other scripts if they are activated. One script can run in merged scenario without other scripts interfering, continuously, without any problems.

 

This is not possible because AHK does not support multiple threads of execution.

 

Running included scripts

Favmenu & Editor can be run as standalone or merged. Start them standalone first to see how they act when they are called normally. I created some simple menu in menu.ini and set the hotkey for the Favmenu to be Left Windows. Notice the yellow star icon in the tray for the Favmenu and regular AHK icon in the title of the Editor’s GUI. You can see 2 A