-
Notifications
You must be signed in to change notification settings - Fork 227
Add output and labels to quickcmd #1534
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -34,17 +34,33 @@ local json = require('json') | |||||||||||||||
| local gui = require('gui') | ||||||||||||||||
| local widgets = require('gui.widgets') | ||||||||||||||||
|
|
||||||||||||||||
| local CONFIG_FILE = 'dfhack-config/quickcmd.json' | ||||||||||||||||
| local CONFIG_FILE_OLD = 'dfhack-config/quickcmd.json' | ||||||||||||||||
| local CONFIG_FILE = 'dfhack-config/quickcmd-v2.json' | ||||||||||||||||
| local HOTKEYWIDTH = 7 | ||||||||||||||||
| local OUTWIDTH = 4 | ||||||||||||||||
| local HOTKEYS = 'asdfghjklqwertyuiopzxcvbnm' | ||||||||||||||||
|
|
||||||||||||||||
| local function save_commands(data) | ||||||||||||||||
| json.encode_file(data, CONFIG_FILE) | ||||||||||||||||
| end | ||||||||||||||||
|
|
||||||||||||||||
| local function load_commands() | ||||||||||||||||
| -- Try to load from new config file first | ||||||||||||||||
| local ok, data = pcall(json.decode_file, CONFIG_FILE) | ||||||||||||||||
| return ok and data or {} | ||||||||||||||||
| end | ||||||||||||||||
| if ok then return data end | ||||||||||||||||
|
|
||||||||||||||||
| local function save_commands(data) | ||||||||||||||||
| json.encode_file(data, CONFIG_FILE) | ||||||||||||||||
| -- New file doesn't exist or is invalid - try old file for migration | ||||||||||||||||
| ok, data = pcall(json.decode_file, CONFIG_FILE_OLD) | ||||||||||||||||
| if not ok then return {} end | ||||||||||||||||
|
|
||||||||||||||||
| -- Migrate old string format to new object format (in-memory only) | ||||||||||||||||
| for i, cmd in ipairs(data) do | ||||||||||||||||
| if type(cmd) == 'string' then | ||||||||||||||||
| data[i] = {command = cmd, name = '', show_output = false} | ||||||||||||||||
| end | ||||||||||||||||
| end | ||||||||||||||||
|
|
||||||||||||||||
| return data | ||||||||||||||||
| end | ||||||||||||||||
|
|
||||||||||||||||
| QCMDDialog = defclass(QCMDDialog, widgets.Window) | ||||||||||||||||
|
|
@@ -61,12 +77,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{ | ||||||||||||||||
|
|
@@ -75,53 +91,132 @@ 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.ZScreenModal) | ||||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I do not see any compelling reason why the output dialog should be modal. On the contrary, I can definitely see myself looking at the output and then trying to match it to various places inside the fort. |
||||||||||||||||
| OutputDialog.ATTRS{ | ||||||||||||||||
| focus_path='quickcmd_output', | ||||||||||||||||
| command='', | ||||||||||||||||
| name='', | ||||||||||||||||
| output='', | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| function OutputDialog:init() | ||||||||||||||||
| local title | ||||||||||||||||
| if self.name and self.name ~= '' then | ||||||||||||||||
| title = self.name .. ': ' .. self.command | ||||||||||||||||
| else | ||||||||||||||||
| title = self.command | ||||||||||||||||
| end | ||||||||||||||||
|
Comment on lines
+163
to
+168
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
This assumes that |
||||||||||||||||
| 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 and cmd_obj.name ~= '' and 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 | ||||||||||||||||
|
|
@@ -148,7 +243,7 @@ function QCMDDialog:onAddCommand() | |||||||||||||||
| COLOR_GREEN, | ||||||||||||||||
| '', | ||||||||||||||||
| function(command) | ||||||||||||||||
| table.insert(self.commands, command) | ||||||||||||||||
| table.insert(self.commands, {command=command, name='', show_output=false}) | ||||||||||||||||
| save_commands(self.commands) | ||||||||||||||||
| self:updateList() | ||||||||||||||||
| end | ||||||||||||||||
|
|
@@ -165,7 +260,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) | ||||||||||||||||
|
|
@@ -187,15 +282,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 | ||||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If the user enters the empty string for the command name, that should probably unset (i.e. set to |
||||||||||||||||
| 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', | ||||||||||||||||
|
|
||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ab9rf Is there a policy for updating the configuration format for tools that are not world or site specific? I would tend to keep the configuration filename aligned with the name of the tool without adding any version suffixes, putting any versioning information about the configuration file format into the configuration file itself. That is, back up the old configuration file under a new name and then convert the configuration in place.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm inclined to agree with you here: keep the file name the same and store the version information into the file itself.