rodent1 Posted July 25, 2012 Share Posted July 25, 2012 (edited) When translation companies send back their translated strings, accelerator keys (alt keys to call a control or menu from the keyboard, marked by an "&" before the key, which Windows displays as an underlined character) typically come back all messed up, with duplicate alt keys everywhere. Going through all menus of an app to fix that can be tedious. So here is a script that will detect all duplicates, and if "offer suggestions" is checked, it will also do its best to come up with a working solution.The script displays the desktop-child visible windows in a listbox. Select the window you want to check, and click on the button below. The results are displayed in a message box, use ctrl-C to copy its contents so you can paste it where it's convenient.The algorithm makes a first pass looking for easy fixes, looping through all characters in each menu entry with a duplicate alt key till a character if found that is not in the list of already-used keys. If that fails for at least one menu entry, suggestions are generated that rely on less frequently used characters to try patterns. It should work most of the time, as long as the menus are not too huge.If you come up with ways that will always work, yet not take too long, please share your improvements! Enjoy... The code: expandcollapse popup#Region ;**** Directives created by AutoIt3Wrapper_GUI **** #AutoIt3Wrapper_UseX64=n #AutoIt3Wrapper_Res_Fileversion=1.0 #AutoIt3Wrapper_Res_Fileversion_AutoIncrement=p #AutoIt3Wrapper_Res_Language=1033 #EndRegion ;**** Directives created by AutoIt3Wrapper_GUI **** #include <guiconstantsex.au3> #include "WinAPI.au3" #Include <GuiMenu.au3> #include <ListViewConstants.au3> #include <WindowsConstants.au3> Dim $ReportBuf = "" Main() Func Main() ShowWinList() EndFunc Func ShowWinList() Local $frmMain = GUICreate("Accelerator Key Menu Tester", 623, 450, 192, 124) Local $lbWindows = GUICtrlCreateList("", 10, 10, 600, 400) Local $arWinData = WinList() Local $WinData for $i = 1 to $arWinData[0][0] if _WinAPI_IsWindowVisible($arWinData[$i][1]) Then Local $sWinTitle = $arWinData[$i][0] if StringLen($sWinTitle) > 0 Then if BitAND(WinGetState($arWinData[$i][1]),16)=0 Then $WinData &= "|" & $sWinTitle & ";" & $arWinData[$i][1] & ";" & $arWinData[$i][0] EndIf EndIf EndIf Next if StringLen($WinData) > 0 Then $WinData = StringRight($WinData, StringLen($WinData) - 1) GUICtrlSetData($lbWindows, $WinData) EndIf Local $btnTestMnuAcKeyDupes = GUICtrlCreateButton("Test Duplicate Menu Accelerator Keys", 10, 420, 200, 25) GUICtrlSetState(-1, $GUI_DISABLE) Local $chkOfferSuggestions = GUICtrlCreateCheckbox("Offer Suggestions", 220, 420, 200, 25) GUICtrlSetState(-1, $GUI_DISABLE) GUISetState(@SW_SHOW) Local $nMsg while 1 $nMsg = GUIGetMsg() Switch $nMsg Case $GUI_EVENT_CLOSE Exit case $lbWindows ; should fire when a listbox selection is made GUICtrlSetState($btnTestMnuAcKeyDupes, $GUI_ENABLE) ; enable the button GUICtrlSetState($chkOfferSuggestions, $GUI_ENABLE) ; enable the checkbox case $btnTestMnuAcKeyDupes Local $hwnd = GetSplitToken(GUICtrlRead($lbWindows), 2, ";") TestMenu($hwnd, GUICtrlRead($chkOfferSuggestions) = $GUI_CHECKED) MsgBox(262144,'Duplicate Accelerator Key Info',"Test Window Title = '" & GetSplitToken(GUICtrlRead($lbWindows), 1, ";") & "'" & $ReportBuf) EndSwitch WEnd EndFunc ; check the accelerator keys for duplicates in all child menu entries of parent menu ; $hMainMenu = handle of parent menu ; $ReportString = string that will precede the list of found accelerator keys ; $ParentMenuPath = '>'-separated menu access path Func CheckDupeMenuAccKeys($hParentMenu, $ReportString, $ParentMenuPath, $bOfferSuggestions) Local $MenuEntryNum = _GUICtrlMenu_GetItemCount($hParentMenu) if $MenuEntryNum <= 0 Then Return EndIf Local $arMnuTxt[1] ; array to hold all menu texts found below ReDim $arMnuTxt[$MenuEntryNum+1];$arMnuTxt[0]+1] $arMnuTxt[0]=$MenuEntryNum Local $AltKeysList = "" ; list of found accelerator keys Local $iAltKeyIndex = 0 ; index into the array $arMnuTxt if StringLen($ParentMenuPath)>0 Then ; append a separator if the parent menu text(s) is provided $ParentMenuPath &= ">" EndIf Local $bFoundDuplicate = False for $iMnuEntry = 0 to $MenuEntryNum Local $MnuTxt = _GUICtrlMenu_GetItemText($hParentMenu,$iMnuEntry) if StringLen($MnuTxt)>0 Then Local $AmpOffset = StringInStr($MnuTxt, "&") if $AmpOffset>0 Then $iAltKeyIndex += 1 $arMnuTxt[$iAltKeyIndex]=$MnuTxt Local $AltChrOffset = StringInStr($AltKeysList, StringUpper(StringMid($MnuTxt, $AmpOffset+1, 1))) if $AltChrOffset>0 Then $ReportBuf &= @CRLF & 'Duplicate menu accelerator key ' & StringMid($MnuTxt, $AmpOffset+1, 1) & ', ' & $ParentMenuPath & $MnuTxt & " conflicts with " & $ParentMenuPath & $arMnuTxt[$AltChrOffset] & " (menu entry #" & $iMnuEntry & ")" $bFoundDuplicate = True EndIf $AltKeysList &= StringUpper(StringMid($MnuTxt, $AmpOffset+1, 1)) EndIf Local $hMenu = _GUICtrlMenu_GetItemSubMenu($hParentMenu, $iMnuEntry) CheckDupeMenuAccKeys($hMenu, 'Accelerator keys used in child menus of "' & $ParentMenuPath & $MnuTxt & '"', $ParentMenuPath & $MnuTxt, $bOfferSuggestions) EndIf Next if StringLen($AltKeysList)>0 Then $ReportBuf &= @CRLF & $ReportString & "=" & $AltKeysList if $bFoundDuplicate And $bOfferSuggestions Then Local $Suggestions = GenerateSimpleSuggestions($AltKeysList, $arMnuTxt) if StringLen($Suggestions)=0 Then ; basic suggestion generation failed $Suggestions = GenerateSuggestions($arMnuTxt) ; strip existing alt keys, and restart from scratch EndIf $ReportBuf &= @CRLF & $Suggestions EndIf EndIf EndFunc ; a character is acceptable for an alt key if it's not a space, and it's not a foreign character (accented like ü, or like ç, ß) that not all keyboards could use. func IsAcceptable($Character) if $Character=" " Or (asc($Character)>127 And $Character<254) then Return False EndIf Return True EndFunc ; Called when a duplicate menu accelerator is found and Generate Simple Suggestions was not completely successful ; Purpose: generate a suggestion for a unique accelerator for each entry with a duplicate accelerator ; Strip all &'s from all menu entries, if there is a "&&", replace it with "~~" as a place marker, then ; Make a list of chars found in list of menu entries with no "&" and their frequencies, then ; loop through list of chars with frequency 1 and place an & in front of them in menu texts that don't have one yet, then ; make a list of chars found in list of menu entries with no "&" and their frequencies, skipping the ones that are already attributed, then ; loop through all remaining list of chars with frequency 1 and place an & in front of them in menu texts that don't have one yet, then ; repeat in a loop till there is no change compared to previous loop or till all menu entries have an "&", then ; if not all menu entries have an "&", repeat with lowest frequency instead of looking for a frequency of 1. ; ; $arMenuText = array of menu texts found in the current menu scope ; returns: string containing suggestion information Func GenerateSuggestions($arMenuText) ; 1- Strip all '&'s from all menu entries Local $arWorkMenuText[1] ReDim $arWorkMenuText[$arMenuText[0]+1] $arWorkMenuText[0]=$arMenuText[0] Local $arUnresolvedCharFreq[1] ReDim $arUnresolvedCharFreq[$arMenuText[0]+1] $arUnresolvedCharFreq[0]=$arMenuText[0] Local $AltKeysList = "" Local $bSolutionIncomplete = False for $i = 1 to $arMenuText[0] if StringInStr($arMenuText[$i], "&&")>0 Then $arWorkMenuText[$i] = StringReplace($arMenuText[$i], "&&", "~~") ; place marker "~~", to be replaced at the end with a "&" Else $arWorkMenuText[$i] = StringReplace($arMenuText[$i], "&", "") EndIf Next ;2- Make a list of chars found in list of menu entries with no "&" and their frequencies Local $arCharList[1] redim $arCharList[$arWorkMenuText[0]+1] $arCharList[0]=$arWorkMenuText[0] for $iMnuEntry = 1 to $arWorkMenuText[0] if StringLen($arWorkMenuText[$iMnuEntry])>0 Then Local $FoundChars = "" for $iChar = 1 to StringLen($arWorkMenuText[$iMnuEntry]) if StringInStr($FoundChars, StringMid($arWorkMenuText[$iMnuEntry], $iChar, 1))=0 Then if IsAcceptable(StringMid($arWorkMenuText[$iMnuEntry], $iChar, 1)) Then $FoundChars &= StringUpper(StringMid($arWorkMenuText[$iMnuEntry], $iChar, 1)) EndIf EndIf Next Local $AllUnprocessedMnuTxt = stringJoin($arWorkMenuText, "|~", $iMnuEntry+1, -1, False) ;join of all $arMenuText starting with next $iMnuEntry if StringLen($AllUnprocessedMnuTxt)>0 Then ; find first character in $FoundChars that is not found in $AllUnprocessedMnuTxt, if any Local $bResolved = False for $iChar = 1 to StringLen($FoundChars) if StringInStr($AllUnprocessedMnuTxt, StringMid($FoundChars, $iChar, 1))=0 Then Local $CharLoc = StringInStr(StringUpper($arWorkMenuText[$iMnuEntry]), StringMid($FoundChars, $iChar, 1)) $arWorkMenuText[$iMnuEntry] = StringLeft($arWorkMenuText[$iMnuEntry], $CharLoc-1) & "&" & StringRight($arWorkMenuText[$iMnuEntry], StringLen($arWorkMenuText[$iMnuEntry]) - $CharLoc + 1) $AltKeysList &= StringUpper(StringMid($FoundChars, $iChar, 1)) $bResolved = True ExitLoop EndIf Next if Not $bResolved Then $bSolutionIncomplete = True for $iChar = 1 to StringLen($FoundChars) Local $CharFreq = StringGetSubstrFreq($arWorkMenuText, $iMnuEntry, StringMid($FoundChars, $iChar, 1)) $arUnresolvedCharFreq[$iMnuEntry] &= StringMid($FoundChars, $iChar, 1) & "(" & $CharFreq & ")" Next EndIf Else ; this would have to be the last menu item in scope for $i=1 to StringLen($arWorkMenuText[$iMnuEntry]) if StringInStr($AltKeysList, StringUpper(StringMid($arWorkMenuText[$iMnuEntry], $i, 1)))=0 Then $arWorkMenuText[$iMnuEntry] = StringLeft($arWorkMenuText[$iMnuEntry], $i-1) & "&" & StringRight($arWorkMenuText[$iMnuEntry], StringLen($arWorkMenuText[$iMnuEntry]) - $i + 1) $AltKeysList &= StringUpper(StringMid($arWorkMenuText[$iMnuEntry], $i, 1)) ExitLoop EndIf Next EndIf EndIf Next if $bSolutionIncomplete Then ; for each unresolved menu entry find char with lowest frequency not in $AltKeysList, use it, add it to $AltKeysList for $iMnuEntry=1 to $arWorkMenuText[0] if StringLen($arUnresolvedCharFreq[$iMnuEntry])>0 Then Local $arTokens = StringSplit($arUnresolvedCharFreq[$iMnuEntry], ")") Local $MinFreq=999 Local $CurChar="" for $iToken = 1 to $arTokens[0] Local $CurFreq = StringRight($arTokens[$iToken], StringLen($arTokens[$iToken])-StringInStr($arTokens[$iToken], "(")) if $CurFreq*1<$MinFreq And $CurFreq*1>0 Then Local $FoundChar = StringLeft($arTokens[$iToken], 1) if StringInStr($AltKeysList, $FoundChar)=0 Then $MinFreq=$CurFreq*1 $CurChar = $FoundChar EndIf EndIf Next if StringLen($FoundChar)>0 Then Local $CharLoc = StringInStr(StringUpper($arWorkMenuText[$iMnuEntry]), $CurChar) $arWorkMenuText[$iMnuEntry] = StringLeft($arWorkMenuText[$iMnuEntry], $CharLoc-1) & "&" & StringRight($arWorkMenuText[$iMnuEntry], StringLen($arWorkMenuText[$iMnuEntry]) - $CharLoc + 1) $AltKeysList &= StringUpper($CurChar) EndIf EndIf Next EndIf Return "Suggestions: " & stringJoin($arWorkMenuText, @CRLF, 1, -1, False) & @CRLF & "Resulting Alt keys list" & @CRLF & $AltKeysList & "-----------" EndFunc ; Called when a duplicate menu accelerator is found ; Purpose: generate a suggestion for a unique accelerator for each entry with a duplicate accelerator ; only a simple algorythm is implemented: ; find all menu entries with a duplicate accelerator key, then ; loop through all characters in each such menu entry till a character if found that is not in the list of used accelerator keys, then ; place the "&" in front of the found unique character, then ; if not all menus have a suggestion, offer no suggestion ; $AltKeysList = list of accelerator keys found in the current menu scope ; $arMenuText = array of menu texts found in the current menu scope ; returns: string containing suggestion information Func GenerateSimpleSuggestions($AltKeysList, $arMenuText) Local $SuggestionRet ; $AltKeysList is a list of the accelerator keys. Record the duplicate indices in a pipe-separated list of comma-separated duplicate indices Local $DupeInfoList = "" for $iOut=1 to StringLen($AltKeysList)-1 Local $DupeList = $iOut for $iIn = $iOut+1 to StringLen($AltKeysList) if StringMid($AltKeysList, $iOut, 1) = StringMid($AltKeysList, $iIn, 1) And StringMid($AltKeysList, $iOut, 1)<>" " Then $DupeList &= "," & $iIn $AltKeysList = StringLeft($AltKeysList, $iIn-1) & " " & StringRight($AltKeysList, StringLen($AltKeysList)-$iIn) EndIf Next if StringInStr($DupeList, ",")>0 Then $DupeInfoList &= "|" & $DupeList EndIf Next if StringLen($DupeInfoList)>0 Then if StringLeft($DupeInfoList,1)="|" Then $DupeInfoList = StringRight($DupeInfoList,StringLen($DupeInfoList)-1) EndIf EndIf ; starting with the first duplicate (2nd instance of use of same accelerator key), look for a character that's not in use for $iKey = 1 to StringLen($AltKeysList) if StringMid($AltKeysList, $iKey, 1) = " " Then Local $bSuggestionFound = False ; look at each character in menu entry, find the first one that's not already in use, if any for $iChar = 1 to StringLen($arMenuText[$iKey])-1 Local $MenuChar = StringMid($arMenuText[$iKey], $iChar, 1) if IsAcceptable($MenuChar) And $MenuChar <> "&" Then if StringInStr($AltKeysList, StringUpper($MenuChar))=0 Then $arMenuText[$iKey] = StringReplace($arMenuText[$iKey], "&", "") Local $KeyLoc = StringInStr($arMenuText[$iKey], $MenuChar) $SuggestionRet &= @CRLF & '"' & StringLeft($arMenuText[$iKey], $KeyLoc-1) & "&" & StringRight($arMenuText[$iKey], StringLen($arMenuText[$iKey]) - $KeyLoc + 1) $AltKeysList = StringLeft($AltKeysList, $iKey-1) & StringUpper($MenuChar) & StringRight($AltKeysList, StringLen($AltKeysList) - $iKey) $bSuggestionFound = True ExitLoop EndIf EndIf Next if Not $bSuggestionFound Then ; we don't have a suggestion that would work for everything, cancel existing suggestions Return "" EndIf EndIf Next Return 'Suggestions:"' & $SuggestionRet & '"' & @CRLF & 'New accelerator key list:' & @CRLF & "'" & $AltKeysList & "'" EndFunc ; replicate the vbscript syntax split(string, separator)(index) ; to return the indexth token of the array resulting from the split command Func GetSplitToken($sIn, $iNdx, $sSep) if StringLen($sIn) > 0 Then if StringInStr($sIn, $sSep) > 0 Then Local $arTokens = StringSplit($sIn, $sSep) if $arTokens[0] >= $iNdx Then Return $arTokens[$iNdx] EndIf EndIf EndIf Return "" EndFunc Func StringGetSubstrFreq($arText, $iSkipEntry, $CurChar) Local $FreqRet = 0 for $i = 1 to $arText[0] if StringInStr($arText[$i], "&")=0 Then ; if the menu entry & location is not yet resolved if $i <> $iSkipEntry Then ; if this is not the current menu entry if StringInStr(StringUpper($arText[$i]), $CurChar)>0 Then $FreqRet += 1 EndIf EndIf EndIf Next Return $FreqRet EndFunc ; replicate the vbscript syntax of Join function, strArrayItemList = join(ItemArray, strSeparator) Func stringJoin($arIn, $Sep = "|", $IndexStart = 0, $IndexEnd = -1, $bIncludeEmpties = True) if $IndexEnd = -1 Then $IndexEnd = UBound($arIn) - 1 EndIf if $IndexEnd<$IndexStart Then Return "" EndIf Local $Ret for $i = $IndexStart to $IndexEnd if (Not $bIncludeEmpties And StringLen($arIn[$i])>0) Or $bIncludeEmpties Then $Ret &= $Sep & $arIn[$i] EndIf Next if StringLeft($Ret, StringLen($Sep)) = $Sep Then $Ret = StringRight($Ret, StringLen($Ret) - StringLen($Sep)) EndIf Return $Ret EndFunc Func TestMenu($hwndParentWin, $bOfferSuggestions) Local $hMainMenu = _GUICtrlMenu_GetMenu($hwndParentWin) CheckDupeMenuAccKeys($hMainMenu, "Accelerator keys used in main menu header", "", $bOfferSuggestions) if StringInStr($ReportBuf, "Duplicate menu accelerator")=0 Then $ReportBuf = "No duplicate accelerator keys were found!" & @CRLF & @CRLF & $ReportBuf EndIf EndFuncAccelerator Key Menu Tester_1.0.zip Edited July 25, 2012 by rodent1 Link to comment Share on other sites More sharing options...
Recommended Posts
Create an account or sign in to comment
You need to be a member in order to leave a comment
Create an account
Sign up for a new account in our community. It's easy!
Register a new accountSign in
Already have an account? Sign in here.
Sign In Now