Skip to content
1 change: 1 addition & 0 deletions changelog.txt
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ Template for new versions:
## New Tools

## New Features
- `gui/quickcmd`: added custom command names and option to display command output
- `gui/notify`: new notification type: missing nemesis records; displays a warning message about game corruption.

## Fixes
Expand Down
175 changes: 156 additions & 19 deletions gui/quickcmd.lua
Original file line number Diff line number Diff line change
Expand Up @@ -34,17 +34,45 @@ local json = require('json')
local gui = require('gui')
local widgets = require('gui.widgets')

local CONFIG_FILE_BACKUP = 'dfhack-config/quickcmd.json.bak'
local CONFIG_FILE = 'dfhack-config/quickcmd.json'
local HOTKEYWIDTH = 7
local OUTWIDTH = 4
local HOTKEYS = 'asdfghjklqwertyuiopzxcvbnm'

local function save_commands(data)
json.encode_file({version=2, commands=data}, CONFIG_FILE)
end

local function migrate_to_v2(data)
json.encode_file(data, CONFIG_FILE_BACKUP)

local commands = {}
for i, cmd in ipairs(data) do
if type(cmd) == 'string' then
table.insert(commands, {command = cmd, name = nil, show_output = false})
end
end

save_commands(commands)
return commands
end

local function load_commands()
local ok, data = pcall(json.decode_file, CONFIG_FILE)
return ok and data or {}
end
if ok then
if type(data) == 'table' and data.version then
if data.version == 2 then
return data.commands or {}
end
end
-- Old format: array of strings
if type(data) == 'table' and #data > 0 then
return migrate_to_v2(data)
end
end

local function save_commands(data)
json.encode_file(data, CONFIG_FILE)
return {}
end

QCMDDialog = defclass(QCMDDialog, widgets.Window)
Expand All @@ -61,12 +89,12 @@ function QCMDDialog:init(info)
self:addviews{
widgets.Label{
frame={t=0},
text={{text='Hotkey', width=HOTKEYWIDTH}, ' Command'},
text={{text='Hotkey', width=HOTKEYWIDTH}, {text='Out', width=OUTWIDTH}, 'Name/Command'},
visible=function() return #self.commands > 0 end,
},
widgets.List{
view_id='list',
frame={t=2, b=3},
frame={t=2, b=4},
on_submit=self:callback('submit'),
},
widgets.Label{
Expand All @@ -75,53 +103,128 @@ function QCMDDialog:init(info)
visible=function() return #self.commands == 0 end,
},
widgets.HotkeyLabel{
frame={b=1, l=0},
frame={b=2, l=0},
key='CUSTOM_SHIFT_A',
label='Add command',
auto_width=true,
on_activate=self:callback('onAddCommand'),
},
widgets.HotkeyLabel{
frame={b=1, l=19},
frame={b=2, l=19},
key='CUSTOM_SHIFT_D',
label='Delete command',
auto_width=true,
on_activate=self:callback('onDelCommand'),
},
widgets.HotkeyLabel{
frame={b=0, l=0},
frame={b=1, l=0},
key='CUSTOM_SHIFT_E',
label='Edit command',
auto_width=true,
on_activate=self:callback('onEditCommand'),
},
widgets.HotkeyLabel{
frame={b=1, l=19},
key='CUSTOM_SHIFT_N',
label='Edit name',
auto_width=true,
on_activate=self:callback('onSetName'),
},
widgets.HotkeyLabel{
frame={b=0, l=0},
key='CUSTOM_SHIFT_O',
label='Capture output',
auto_width=true,
on_activate=self:callback('onToggleOutput'),
},
}

self:updateList()
end

function QCMDDialog:submit(idx, choice)
local cmd_obj = self.commands[idx]

if cmd_obj.show_output then
self:showCommandOutput(cmd_obj.command, cmd_obj.name)
else
local screen = self.parent_view
dfhack.screen.hideGuard(screen, function()
dfhack.run_command(cmd_obj.command)
end)
screen:dismiss()
end
end

function QCMDDialog:showCommandOutput(command, name)
local output = dfhack.run_command_silent(command)

-- Dismiss the quickcmd dialog before showing output
local screen = self.parent_view
dfhack.screen.hideGuard(screen, function()
dfhack.run_command(choice.command)
end)
screen:dismiss()

local OutputDialog = defclass(OutputDialog, gui.ZScreen)
OutputDialog.ATTRS{
focus_path='quickcmd_output',
command='',
name='',
output='',
}

function OutputDialog:init()
local title = ('%s%s'):format(self.name ~= '' and self.name .. ': ' or '', self.command)

self:addviews{
widgets.Window{
frame_title=title,
frame={w=80, h=25},
resizable=true,
resize_min={h=10, w=40},
subviews={
widgets.WrappedLabel{
view_id='output',
frame={t=0, l=0, r=0, b=2},
text_to_wrap=self.output or 'No output',
scroll_keys=widgets.STANDARDSCROLL,
},
widgets.HotkeyLabel{
frame={b=0, l=0},
key='LEAVESCREEN',
label='Close',
auto_width=true,
on_activate=self:callback('dismiss'),
},
}
}
}
end

if #output == 0 then
output = 'Command finished successfully'
end

OutputDialog{command=command, name=name, output=output}:show()
end

function QCMDDialog:updateList()
-- Build the list entries.
local choices = {}
for i,command in ipairs(self.commands) do
for i,cmd_obj in ipairs(self.commands) do
-- Get the hotkey for this entry.
local hotkey = nil
if i <= HOTKEYS:len() then
hotkey = HOTKEYS:sub(i, i)
end

-- Display name if set, otherwise display command
local display_text = cmd_obj.name or cmd_obj.command

-- Store the entry.
table.insert(choices, {
text={{text=hotkey or '', width=HOTKEYWIDTH}, ' ', command},
command=command,
text={{text=hotkey or '', width=HOTKEYWIDTH}, {text=cmd_obj.show_output and '[X]' or '[ ]', width=OUTWIDTH}, display_text},
command=cmd_obj.command,
name=cmd_obj.name,
show_output=cmd_obj.show_output,
hotkey=hotkey and ('CUSTOM_' .. hotkey:upper()) or '',
})
end
Expand All @@ -148,7 +251,7 @@ function QCMDDialog:onAddCommand()
COLOR_GREEN,
'',
function(command)
table.insert(self.commands, command)
table.insert(self.commands, {command=command, name=nil, show_output=false})
save_commands(self.commands)
self:updateList()
end
Expand All @@ -165,7 +268,7 @@ function QCMDDialog:onDelCommand()
-- Prompt for confirmation.
dlg.showYesNoPrompt(
'Delete command',
'Are you sure you want to delete this command: ' .. NEWLINE .. item.command,
'Are you sure you want to delete this command: ' .. NEWLINE .. self.commands[index].command,
COLOR_GREEN,
function()
table.remove(self.commands, index)
Expand All @@ -187,15 +290,49 @@ function QCMDDialog:onEditCommand()
'Edit command',
'Enter command:',
COLOR_GREEN,
item.command,
self.commands[index].command,
function(command)
self.commands[index] = command
self.commands[index].command = command
save_commands(self.commands)
self:updateList()
end
)
end

function QCMDDialog:onSetName()
-- Get the selected command.
local index, item = self.subviews.list:getSelected()
if not item then
return
end

-- Prompt for new name.
dlg.showInputPrompt(
'Set name',
'Enter name:',
COLOR_GREEN,
self.commands[index].name or '',
function(name)
self.commands[index].name = name ~= '' and name or nil
save_commands(self.commands)
self:updateList()
end
)
end

function QCMDDialog:onToggleOutput()
-- Get the selected command.
local index, item = self.subviews.list:getSelected()
if not item then
return
end

-- Toggle the show_output flag.
self.commands[index].show_output = not self.commands[index].show_output
save_commands(self.commands)
self:updateList()
end

QCMDScreen = defclass(QCMDScreen, gui.ZScreen)
QCMDScreen.ATTRS {
focus_path='quickcmd',
Expand Down