1+ ; AutoHotkey v2 Script - F-Key Manager GUI (Improved)
2+ ; This script creates a GUI for managing F1-F12 key assignments
3+
4+ #SingleInstance Force
5+ #Include globals.ahk
6+
7+ ; ============================================================================
8+ ; UTILITY FUNCTIONS
9+ ; ============================================================================
10+
11+ ; Check if an array contains a specific value
12+ array_has_value (haystack, needle ) {
13+ for index, value in haystack {
14+ if (value = needle)
15+ return true
16+ }
17+ return false
18+ }
19+
20+ ; Read INI file section and return as Map
21+ get_ini_as_map (ini_file_path, ini_section ) {
22+ section_records := Map()
23+
24+ if ! FileExist (ini_file_path) {
25+ MsgBox (" Config file not found: " . ini_file_path, " Error" , " Icon!" )
26+ return section_records
27+ }
28+
29+ try {
30+ section_data := IniRead (ini_file_path, ini_section)
31+ } catch Error as err {
32+ MsgBox (" Failed to read section '" . ini_section . " ' from: " . ini_file_path . " `nError: " . err.message, " Error" , " Icon!" )
33+ return section_records
34+ }
35+
36+ ; Parse the section data
37+ loop parse , section_data, " `n" , " `r" {
38+ line := Trim (A_LoopField )
39+ if (line = "" || SubStr (line, 1 , 1 ) = " ;" ) ; Skip empty lines and comments
40+ continue
41+
42+ pos := InStr (line, " =" )
43+ if (pos > 0 ) {
44+ key := Trim (SubStr (line, 1 , pos - 1 ))
45+ value := Trim (SubStr (line, pos + 1 ))
46+ if (key ! = "" )
47+ section_records[key] := value
48+ }
49+ }
50+
51+ return section_records
52+ }
53+
54+ ; File chooser dialog
55+ choose_file () {
56+ try {
57+ file_path := FileSelect(3 , , " Select an executable file" , " Executable Programs (*.exe)" )
58+ return file_path ; Returns empty string if cancelled, not 0
59+ } catch Error as err {
60+ MsgBox (" Error selecting file: " . err.message, " File Selection Error" , " Icon!" )
61+ return ""
62+ }
63+ }
64+
65+ ; Get description text for function key assignment
66+ get_reference_text (program_path ) {
67+ if (program_path = "" )
68+ return " Click Browse to choose a program to run"
69+
70+ SplitPath (program_path, & basename)
71+
72+ switch basename {
73+ case " default_browser" :
74+ return " Launch / cycle through the default browser window(s)"
75+ case " all_browsers" :
76+ return " Launch default browser / cycle through all active browsers"
77+ case " browser_tabs" :
78+ return " Launch default/active browser / cycle through active browser tabs"
79+ case " switch_window" :
80+ return " Switch between two most recent active windows (Alt+Tab)"
81+ case " switch_tabs" :
82+ return " Switch between multiple tabs in the same window (Ctrl+Tab)"
83+ default :
84+ return " Launch / switch to '" . basename . " '"
85+ }
86+ }
87+
88+ ; Save configuration to INI file
89+ save_configuration () {
90+ try {
91+ for index, controls in g_FKeyControls {
92+ key := " f" . index
93+ selected_index := controls.dropdown.Value
94+ selected_value := g_FKeyManager.dropdown_values[selected_index]
95+ edit_value := controls.edit .Text
96+
97+ ; Determine what to save based on dropdown selection
98+ value_to_save := (selected_value = " add_app_path" ) ? edit_value : selected_value
99+
100+ IniWrite (value_to_save, config_path, function_keys_section, key)
101+ }
102+ MsgBox (" Configuration saved successfully!" , " Success" , " IconI" )
103+ return true
104+ } catch Error as err {
105+ MsgBox (" Failed to save configuration: " . err.message, " Error" , " Icon!" )
106+ return false
107+ }
108+ }
109+
110+ ; ============================================================================
111+ ; GUI CLASS DEFINITION
112+ ; ============================================================================
113+
114+ class fkey_manager_gui {
115+ __New () {
116+ this.gui := ""
117+ this.controls := []
118+ this.function_keys_details := Map()
119+ this.text_labels := [" f1" , " f2" , " f3" , " f4" , " f5" , " f6" , " f7" , " f8" , " f9" , " f10" , " f11" , " f12" ]
120+ this.dropdown_options := [" Custom Program" , " Default Browser" , " All Browsers" , " Browser Tabs" , " Switch Window" , " Switch Tabs" ]
121+ this.dropdown_values := [" add_app_path" , " default_browser" , " all_browsers" , " browser_tabs" , " switch_window" , " switch_tabs" ]
122+ }
123+
124+ Create () {
125+ ; Load current configuration
126+ this.function_keys_details := get_ini_as_map(config_path, function_keys_section)
127+
128+ ; Create main GUI
129+ this.gui := Gui (, " Function Keys Manager - " . A_ScriptName )
130+ this.gui .OnEvent(" Close" , (* ) => this.gui .Hide())
131+ this.gui .OnEvent(" Size" , (* ) => this.OnResize())
132+
133+ ; Add header
134+ this.gui .Add(" Text" , " w420 h30 x10 y10" , " Function Key Assignment Manager" ).SetFont(" s12" )
135+ this.gui .Add(" Text" , " w420 h20 x10 y40" , " Configure what each Function key (F1 - F12) should do when pressed" ).SetFont(" s9" )
136+
137+ ; Create controls for each F-key
138+ this.CreateFKeyControls()
139+
140+ ; Add action buttons
141+ this.CreateActionButtons()
142+
143+ ; Store reference globally for save function
144+ global g_FKeyControls := this.controls
145+
146+ return this.gui
147+ }
148+
149+ CreateFKeyControls () {
150+ y_pos := 70
151+
152+ Loop this.text_labels.Length {
153+ index := A_Index
154+ key := this.text_labels[index]
155+ current_value := this.function_keys_details.Get(key, "" )
156+
157+ ; Create control group for this F-key
158+ group := {}
159+
160+ ; F-key label
161+ group.label := this.gui .Add(" Text" , " w25 h20 x10 y" . y_pos . " Center" , StrUpper(key) . " :" )
162+
163+ ; Dropdown for action type
164+ group.dropdown := this.gui .Add(" DropDownList" , " w110 h20 x40 y" . y_pos, this.dropdown_options)
165+ group.dropdown.OnEvent(" Change" , this.OnDropdownChange.Bind(this, index))
166+
167+ ; Edit box for custom path
168+ group.edit := this.gui .Add(" Edit" , " w270 h20 x155 y" . y_pos, current_value)
169+ group.edit .OnEvent(" Change" , this.OnEditChange.Bind(this, index))
170+
171+ ; Browse button
172+ group.browse_btn := this.gui .Add(" Button" , " w50 h20 x375 y" . y_pos + 22 , " Browse" )
173+ group.browse_btn.OnEvent(" Click" , this.OnBrowseClick.Bind(this, index))
174+
175+ ; Description text
176+ group.desc_text := this.gui .Add(" Text" , " w330 h20 x40 y" . (y_pos + 22 ), get_reference_text(current_value))
177+ group.desc_text.SetFont(" s8 Italic c0x666666" )
178+
179+ ; Set initial dropdown selection and edit box state
180+ if (array_has_value(this.dropdown_values, current_value) && current_value ! = "" ) {
181+ ; Find the index of the value and select corresponding option
182+ for i, val in this.dropdown_values {
183+ if (val = current_value) {
184+ group.dropdown.Choose(i)
185+ break
186+ }
187+ }
188+ group.edit .Text := current_value
189+ group.edit .Enabled := false
190+ group.browse_btn.Enabled := false
191+ } else {
192+ group.dropdown.Choose(1 ) ; "Custom Program"
193+ group.edit .Text := current_value ; Show the custom path
194+ group.edit .Enabled := true
195+ group.browse_btn.Enabled := true
196+ }
197+
198+ this.controls.Push(group )
199+ y_pos + = 45
200+ }
201+ }
202+
203+ CreateActionButtons () {
204+ y_pos := 70 + (this.text_labels.Length * 45 ) + 15
205+
206+ ; Save button
207+ save_btn := this.gui .Add(" Button" , " w70 h30 x10 y" . y_pos, " Save" )
208+ save_btn.OnEvent(" Click" , (* ) => save_configuration())
209+
210+ ; Restore button
211+ restore_btn := this.gui .Add(" Button" , " w70 h30 x90 y" . y_pos, " Restore" )
212+ restore_btn.OnEvent(" Click" , (* ) => this.RestoreConfig())
213+
214+ ; Clear button
215+ clear_btn := this.gui .Add(" Button" , " w70 h30 x170 y" . y_pos, " Clear All" )
216+ clear_btn.OnEvent(" Click" , (* ) => this.ClearAll())
217+
218+ ; Close button
219+ close_btn := this.gui .Add(" Button" , " w70 h30 x355 y" . y_pos, " Close" )
220+ close_btn.OnEvent(" Click" , (* ) => this.gui .Hide())
221+ }
222+
223+ OnDropdownChange (index, * ) {
224+ group := this.controls[index]
225+ selected_index := group.dropdown.Value
226+ selected_value := this.dropdown_values[selected_index]
227+
228+ if (selected_value = " add_app_path" ) {
229+ group.edit .Enabled := true
230+ group.browse_btn.Enabled := true
231+ ; Keep the current custom path if it exists
232+ current_value := this.function_keys_details.Get(this.text_labels[index], "" )
233+ if (! array_has_value(this.dropdown_values, current_value)) {
234+ group.edit .Text := current_value
235+ } else {
236+ group.edit .Text := ""
237+ }
238+ group.edit .Focus()
239+ } else {
240+ group.edit .Enabled := false
241+ group.browse_btn.Enabled := false
242+ group.edit .Text := selected_value
243+ }
244+
245+ this.UpdateDescription(index)
246+ }
247+
248+ OnEditChange (index, * ) {
249+ this.UpdateDescription(index)
250+ }
251+
252+ OnBrowseClick (index, * ) {
253+ group := this.controls[index]
254+ selected_file := choose_file()
255+
256+ if (selected_file ! = "" && selected_file ! = 0 ) { ; choose_file returns 0 on cancel
257+ group.edit .Text := selected_file
258+ ; Ensure dropdown is set to "Custom Program" when browsing for custom file
259+ group.dropdown.Choose(1 )
260+ group.edit .Enabled := true
261+ group.browse_btn.Enabled := true
262+ this.UpdateDescription(index)
263+ }
264+ }
265+
266+ UpdateDescription (index ) {
267+ group := this.controls[index]
268+ selected_index := group.dropdown.Value
269+ selected_value := this.dropdown_values[selected_index]
270+ current_value := (selected_value = " add_app_path" ) ? group.edit .Text : selected_value
271+ group.desc_text.Text := get_reference_text(current_value)
272+ }
273+
274+ RestoreConfig () {
275+ result := MsgBox (" This will restore all Function keys assignments from the last saved configuration. `n`nAny unsaved changes will be lost. Continue?" , " Restore Configuration" , " YesNo Icon?" )
276+ if (result = " Yes" ) {
277+ ; Reload configuration from file
278+ this.function_keys_details := get_ini_as_map(config_path, function_keys_section)
279+
280+ ; Update all controls with restored values
281+ Loop this.controls.Length {
282+ index := A_Index
283+ group := this.controls[index]
284+ key := this.text_labels[index]
285+ restored_value := this.function_keys_details.Get(key, "" )
286+
287+ ; Set dropdown and edit box based on restored value
288+ if (array_has_value(this.dropdown_values, restored_value) && restored_value ! = "" ) {
289+ ; Find the index of the value and select corresponding option
290+ for i, val in this.dropdown_values {
291+ if (val = restored_value) {
292+ group.dropdown.Choose(i)
293+ break
294+ }
295+ }
296+ group.edit .Text := restored_value
297+ group.edit .Enabled := false
298+ group.browse_btn.Enabled := false
299+ } else {
300+ group.dropdown.Choose(1 ) ; "Custom Program"
301+ group.edit .Text := restored_value
302+ group.edit .Enabled := true
303+ group.browse_btn.Enabled := true
304+ }
305+
306+ this.UpdateDescription(index)
307+ }
308+
309+ MsgBox (" Configuration restored from file successfully!" , " Restore Complete" , " Icon!" )
310+ }
311+ }
312+
313+ ClearAll () {
314+ Loop this.controls.Length {
315+ group := this.controls[A_Index ]
316+ group.dropdown.Choose(1 ) ; "Custom Program"
317+ group.edit .Text := ""
318+ group.edit .Enabled := true
319+ group.browse_btn.Enabled := true
320+ this.UpdateDescription(A_Index )
321+ }
322+ }
323+
324+ OnResize () {
325+ ; Handle window resize if needed
326+ ; This could be expanded to make the GUI more responsive
327+ }
328+
329+ Show () {
330+ this.gui .Show()
331+ }
332+
333+ Hide () {
334+ this.gui .Hide()
335+ }
336+ }
337+
338+ ; ============================================================================
339+ ; MAIN EXECUTION
340+ ; ============================================================================
341+
342+ ; Create global reference
343+ global g_FKeyManager := ""
344+ global g_FKeyControls := []
345+
346+ ; Initialize and show GUI
347+ initialize_fkey_manager (* ) {
348+ global g_FKeyManager
349+ g_FKeyManager := fkey_manager_gui()
350+ g_FKeyManager.Create()
351+ g_FKeyManager.Show()
352+ }
0 commit comments