forked from finale-lua/lua-scripts
-
Notifications
You must be signed in to change notification settings - Fork 0
/
finale_lua_menu_organizer.lua
302 lines (271 loc) · 13.5 KB
/
finale_lua_menu_organizer.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
function plugindef()
finaleplugin.RequireDocument = false
finaleplugin.RequireSelection = false
finaleplugin.NoStore = true
finaleplugin.ExecuteAtStartup = true
finaleplugin.ModifyFinaleMenus = true
finaleplugin.IncludeInPluginMenu = false
finaleplugin.LoadLuaOSUtils = true
finaleplugin.Author = "Robert Patterson"
finaleplugin.Copyright = "2023"
finaleplugin.Version = "1.0.2"
finaleplugin.Date = "2023-02-24"
finaleplugin.MinJWLuaVersion = 0.66 -- https://robertgpatterson.com/-fininfo/-rgplua/rgplua.html
--[[
As of March 10, 2023, the documentation generator for the finale-lua/lua-scripts GitHub repository does not
support Markdown tables, so I rewrote the Notes string not to use one. However, if it ever does support them,
here is the original table.
|Token|Description|
|-----|-----------|
|MENUNAME [text]|Identifies the source menu from which to take menu items. The plugin searches Finale's Plug-Ins menu and submenus for a menu item that starts with this text. The menu containing that item becomes the menu from which items are taken. If this value is omitted, it defaults to JW Lua.|
|USEMAINMENU [text]|Specifies a new menu item to be created in Finale's main menu bar. If omitted, the menu items will be copied to the same menu as specified by MENUNAME. You can include an ampersand (&) for a Windows menu hotkey. This will be stripped out and ignored if the file is used with Mac Finale.|
|>[text]|Starts a submenu with name [text].|
|<|Ends the current submenu.|
|=>|Changes the menu item text from the original text to new text.|
|-|A single hyphen by itself inserts a divider into the menu.|
|//|Delimits a comment. Everything after the comment delimiter is ignored.|
]]
finaleplugin.Notes = [[
This plug-in runs when Finale starts up and organizes the menus according to a configuration file called `finale_lua_menus.txt`.
The plugin searches for the file in two locations:
1. The running folder where this script resides.
2. The Finale Plug-Ins main folder.
If the file is not found, the script exits without doing anything. However, you can have it create a template with all the Lua menu items
by changing the `create_template_if_not_found` variable to `true`. You may then edit the template to taste. It creates it in the script's running folder.
The format of `finale_lua_menus.txt` is fully compatible with the format of the JWLuaMenu plugin, which this script is intended to replace.
It supports the following keywords and tokens. Empty lines are skipped.
**MENUNAME [text]**
Identifies the source menu from which to take menu items. The plugin searches Finale's Plug-Ins menu and submenus for a menu item that starts with this text.
The menu containing that item becomes the menu from which items are taken. If this value is omitted, it defaults to JW Lua.
**USEMAINMENU [text]**
Specifies a new menu item to be created in Finale's main menu bar. If omitted, the menu items will be copied to the same menu as specified by MENUNAME.
You can include an ampersand (&) for a Windows menu hotkey. This will be stripped out and ignored if the file is used with Mac Finale.
**>[text]** Starts a submenu with name [text].
**<** Ends the current submenu.
**=>** Changes the menu item text from the original text to new text.
**-** A single hyphen by itself inserts a divider into the menu.
**//** Delimits a comment. Everything after the comment delimiter is ignored.
Tabs and other whitespace are ignored, but it may be useful to use tabs to show submenus.
If any of the source menus are empty after the menu layout is complete, the script removes that submenu from Finale's plugin menu.
Here is an example of a configuration file.
```
MENUNAME RGP Lua // selects the subfolder containing the RGP Lua plugin.
USEMAINMENU &Lua // creates a new menu called 'Lua' in Finale's main menu bar (optional)
>Articulations
Autoposition Rolled Chord Articulations
Remove Duplicate Articulations
Remove Articulations from Rests
Reset Automatic Articulation Positions
Reset Articulation Positions
-
Delete Articulations => Delete All
<
>Barline
Barline Set Dashed => Dashed
Barline Set Double => Double
Barline Set Final => Final
Barline Set None => None
Barline Set Normal => Normal
<
```
]]
return "Finale Lua Menu Organizer", "Finale Lua Menu Organizer",
"Organizes the Lua menus in Finale's Plug-Ins menu as specified in a configuration file."
end
local create_template_if_not_found = false -- change this value to `true` if you want the script to create a template file.
local utils = require("library.utils")
local osutils = require("luaosutils")
local menu = osutils.menu
local layout_file_name = "finale_lua_menus.txt"
local comment_string = "//"
local menuname_keyword = "MENUNAME"
local usemainmenu_keyword = "USEMAINMENU"
local downsubmenu_indicator = ">"
local upsubmenu_indicator = "<"
local separator_indicator = "-"
local replacement_indicator = "=>"
local win_kbdshortcut = "&"
if finenv.UI():IsOnMac() then
win_kbdshortcut = ""
end
local top_level_menu = menu.get_top_level_menu(finenv.GetFinaleMainWindow())
local min_index_for_plugin_menu = 6 -- Finale's Plug-ins menu is always at or after this index in the main menu
local get_lua_menu = function()
local lua_menu, lua_menu_index = menu.find_item(top_level_menu, "RGP Lua...", min_index_for_plugin_menu)
if lua_menu then return lua_menu, lua_menu_index end
-- we should never get here, because the script requires RGP Lua to run, but just in case:
lua_menu, lua_menu_index = menu.find_item(top_level_menu, "JW Lua...", min_index_for_plugin_menu)
return lua_menu, lua_menu_index
end
-- return path for config file and a boolean that indicates if it already exists
local get_config_file = function()
local try_file = function(file_path)
local file = io.open(file_path, "r")
if file then
io.close(file)
return true
end
return false
end
if try_file(finenv.RunningLuaFolderPath() .. layout_file_name) then
return finenv.RunningLuaFolderPath() .. layout_file_name, true
end
local fcstr = finale.FCString()
fcstr:SetPluginsFolderPath()
fcstr:AppendLuaString("/") -- forward slash works on both Windows and macOS
if try_file(fcstr.LuaString .. layout_file_name) then
return fcstr.LuaString .. layout_file_name, true
end
return finenv.RunningLuaFolderPath() .. layout_file_name, false
end
local create_layout_template = function()
local lua_menu, lua_menu_index = get_lua_menu()
if not lua_menu then return false end
local config_file = get_config_file()
local file = io.open(config_file, "w")
if not file then return false end
file:write(comment_string .. " " .. plugindef() .. " " .. finaleplugin.Version .. "\n")
file:write(comment_string .. " " .. "Use > to start a submenu and < to return to previous menu." .. "\n")
file:write(comment_string .. " " .. "Use (original text) => (replacement text) to change the text of a menu." .. "\n")
file:write(comment_string .. " " .. "Tabs and other whitespace are ignored." .. "\n")
file:write("\n")
file:write(menuname_keyword ..
" " ..
"RGP Lua" ..
" " .. comment_string .. "Optional keyword to choose any menu to organize. (Defaults to RGP Lua if omitted.)" .. "\n")
file:write(usemainmenu_keyword ..
" " .. win_kbdshortcut .. "Lua" .. " " .. comment_string ..
"Remove this line to build the Lua menu in place." .. "\n")
file:write("\n")
local num_items = menu.get_item_count(lua_menu)
local lua_menu_title = menu.get_item_text(lua_menu, lua_menu_index)
local got1 = false
for x = 0, num_items - 1 do
local item_text = menu.get_item_text(lua_menu, x)
if item_text ~= lua_menu_title then
if not got1 then
file:write(downsubmenu_indicator, "Scripts" .. "\n")
got1 = true
end
file:write("\t" .. item_text .. "\n")
end
end
if got1 then
file:write(upsubmenu_indicator .. "\n")
file:write(separator_indicator .. "\n")
end
file:write(lua_menu_title .. "\n")
io.close(file)
return true
end
local parse_layout_file_to_menu -- create local variable for recursive call
parse_layout_file_to_menu = function(file, from_menu, to_menu)
local menus_to_delete = {}
local function function_exit() -- all function exits must use this exit function
for k, _ in pairs(menus_to_delete) do
menu.delete_submenu(k, finenv.GetFinaleMainWindow())
end
return true
end
local function extract_keyword_value(keyword, line)
local result = utils.trim(line:sub(#keyword + 1))
if not finenv.UI():IsOnWindows() then
result = result:gsub("&", "")
end
return result
end
to_menu = to_menu or from_menu
while true do
local line = file:read("*line")
if not line then break end
local comment_start = line:find(comment_string, 1, true)
if comment_start then
line = line:sub(1, comment_start-1)
end
line = utils.trim(line)
if #line > 0 then
if line:find(menuname_keyword, 1, true) == 1 then
line = extract_keyword_value(menuname_keyword, line)
from_menu = menu.find_item(top_level_menu, line, min_index_for_plugin_menu)
to_menu = to_menu or from_menu
elseif line:find(usemainmenu_keyword, 1, true) == 1 then
line = extract_keyword_value(usemainmenu_keyword, line)
local top_menu = menu.get_top_level_menu(finenv.GetFinaleMainWindow())
to_menu = menu.insert_submenu(line, top_menu)
to_menu = to_menu or from_menu -- not likely, but be safe
elseif from_menu and to_menu then
if not finenv.UI():IsOnWindows() then
if not line:find(" & ", 1, true) then
line = line:gsub("&", "")
end
end
if line:find(downsubmenu_indicator, 1, true) == 1 then
local submenu = menu.insert_submenu(line:sub(2), to_menu)
parse_layout_file_to_menu(file, from_menu, submenu)
elseif line:find(upsubmenu_indicator, 1, true) == 1 then
return function_exit()
elseif line:find(separator_indicator, 1, true) == 1 then
menu.insert_separator(to_menu)
else
local find_item = function(start_menu, str)
if finenv.UI():IsOnWindows() then
str = str:gsub("&", "") -- menu.find_item skips "&" on Windows, so leave them out in search
end
return menu.find_item(start_menu, str)
end
local item_menu, item_index = find_item(from_menu, line)
if item_menu then
if menu.move_item(item_menu, item_index, to_menu) then
menus_to_delete[item_menu] = true
menus_to_delete[from_menu] = true
end
else
local found = line:find(replacement_indicator, -#line, true)
if found then
local original_text = utils.trim(line:sub(1, found - 1))
local replacement_text = utils.trim(line:sub(found + #replacement_indicator))
if #replacement_text > 0 then
item_menu, item_index = find_item(from_menu, original_text)
if item_menu then
local success, index = menu.move_item(item_menu, item_index, to_menu)
if success then
menu.set_item_text(to_menu, index, replacement_text)
menus_to_delete[item_menu] = true
menus_to_delete[from_menu] = true
end
end
end
end
end
end
end
end
end
return function_exit()
end
local function organize_finale_lua_menus()
local file_path, exists = get_config_file()
if not exists then
if not create_template_if_not_found then
return
end
if not create_layout_template() then
finenv.UI():AlertError(plugindef() .. " was unable to create a layout template.", "")
return
end
file_path, exists = get_config_file()
if not exists then
finenv.UI():AlertError("An unexpected error occured in " .. plugindef() .. ".", "")
return
end
end
local file = io.open(file_path, "r")
if not file then
finenv.UI():AlertError("Unable to read " .. file_path .. ".", "")
return
end
local lua_menu = get_lua_menu()
parse_layout_file_to_menu(file, lua_menu)
menu.redraw(finenv.GetFinaleMainWindow()) -- does nothing on macOS
end
organize_finale_lua_menus()