From 84fc36d5a74413147897bdb48f8b2478e1c17c7d Mon Sep 17 00:00:00 2001 From: Timur Kelman Date: Sun, 13 Aug 2023 19:08:42 +0200 Subject: [PATCH 01/31] Update workorder-details.lua --- gui/workorder-details.lua | 92 +++++++++++++++++++++++++++++---------- 1 file changed, 68 insertions(+), 24 deletions(-) diff --git a/gui/workorder-details.lua b/gui/workorder-details.lua index b710830590..e9f298d3af 100644 --- a/gui/workorder-details.lua +++ b/gui/workorder-details.lua @@ -1,33 +1,18 @@ -- adjust work orders' input item, material, traits ---[====[ -gui/workorder-details -===================== -Adjust input items, material, or traits for work orders. Actual -jobs created for it will inherit the details. - -This is the equivalent of `gui/workshop-job` for work orders, -with the additional possibility to set input items' traits. - -It has to be run from a work order's detail screen -(:kbd:`j-m`, select work order, :kbd:`d`). - -For best experience add the following to your ``dfhack*.init``:: - - keybinding add D@workquota_details gui/workorder-details - -]====] +--@ module = true --[[ Credit goes to the author of `gui/workshop-job`, it showed me the way. Also, a huge chunk of code could be reused. ]] +local overlay = require('plugins.overlay') + local utils = require 'utils' local gui = require 'gui' local guimat = require 'gui.materials' local widgets = require 'gui.widgets' -local dlg = require 'gui.dialogs' local wsj = reqscript 'gui/workshop-job' @@ -180,11 +165,70 @@ function JobDetails:onChangeTrait() }:show() end -local scr = dfhack.gui.getCurViewscreen() -if not df.viewscreen_workquota_detailsst:is_instance(scr) then - qerror("This script needs to be run from a work order details screen") +local function ScrJobDetails() + return df.global.game.main_interface.job_details +end + +local function show_job_details() + local scr = ScrJobDetails() + if not scr.open -- dfhack.gui.matchFocusString('dwarfmode/JobDetails') + or scr.context ~= df.job_details_context_type.MANAGER_WORK_ORDER + then + qerror("This script needs to be run from a work order details screen") + end + + -- by opening the viewscreen_workquota_detailsst the + -- work order's .items array is initialized + JobDetails{ job = scr.wq }:show() +end + +-- ------------------- +-- RecheckOverlay +-- + +local focusStrings = 'dwarfmode/JobDetails' + +DetailsHotkeyOverlay = defclass(DetailsHotkeyOverlay, overlay.OverlayWidget) +DetailsHotkeyOverlay.ATTRS{ + default_pos={x=5,y=7}, + default_enabled=true, + viewscreens=focusStrings, + frame={w=1+6+2+(7)+1, h=3}, +} + +function DetailsHotkeyOverlay:init() + self:addviews{ + widgets.TextButton{ + view_id = 'button', + frame={t=0, l=0, r=0, h=1}, + label='details', + key='CUSTOM_CTRL_D', + on_activate=show_job_details, + }, + } +end + +function DetailsHotkeyOverlay:onRenderBody(dc) + local scr = ScrJobDetails() + + if (scr.context ~= df.job_details_context_type.MANAGER_WORK_ORDER) then + self.subviews.button.visible = false + return + else + self.subviews.button.visible = true + end + + DetailsHotkeyOverlay.super.onRenderBody(self, dc) +end + +-- ------------------- + +OVERLAY_WIDGETS = { + details=DetailsHotkeyOverlay, +} + +if dfhack_flags.module then + return end --- by opening the viewscreen_workquota_detailsst the --- work order's .items array is initialized -JobDetails{ job = scr.order }:show() +show_job_details() From 1f9c249125e54b14b0ca9acbaafc5a3c1a70587b Mon Sep 17 00:00:00 2001 From: Timur Kelman Date: Sun, 13 Aug 2023 19:13:24 +0200 Subject: [PATCH 02/31] Update workorder-details.rst --- docs/gui/workorder-details.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/gui/workorder-details.rst b/docs/gui/workorder-details.rst index ff3639f436..9cb3642981 100644 --- a/docs/gui/workorder-details.rst +++ b/docs/gui/workorder-details.rst @@ -9,8 +9,7 @@ This tool allows you to adjust item types, materials, and/or traits for items used in manager workorders. The jobs created from those workorders will inherit the details. -Invoke while on a work order's detail screen (:kbd:`j-m`, select work order, -:kbd:`d`). +Invoke while on a work order's detail screen (click on the magnifying glass). Usage ----- From 9eca80b03e620b1ec31433fa8558bb42277383a7 Mon Sep 17 00:00:00 2001 From: Timur Kelman Date: Sun, 13 Aug 2023 19:22:41 +0200 Subject: [PATCH 03/31] remove trailing whitespaces --- gui/workorder-details.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gui/workorder-details.lua b/gui/workorder-details.lua index e9f298d3af..4c93828c12 100644 --- a/gui/workorder-details.lua +++ b/gui/workorder-details.lua @@ -171,7 +171,7 @@ end local function show_job_details() local scr = ScrJobDetails() - if not scr.open -- dfhack.gui.matchFocusString('dwarfmode/JobDetails') + if not scr.open -- dfhack.gui.matchFocusString('dwarfmode/JobDetails') or scr.context ~= df.job_details_context_type.MANAGER_WORK_ORDER then qerror("This script needs to be run from a work order details screen") From 8b559fdff7fed0b12f2c216fd9f8d694183ee64c Mon Sep 17 00:00:00 2001 From: Timur Kelman Date: Sun, 3 Sep 2023 18:24:17 +0200 Subject: [PATCH 04/31] merge workorder-details into workshop-job, update to not use `gui.dialogs` anymore --- .../gui/{workshop-job.rst => job-details.rst} | 25 +- docs/gui/workorder-details.rst | 19 - gui/job-details.lua | 437 ++++++++++++++++++ gui/workorder-details.lua | 234 ---------- gui/workshop-job.lua | 338 -------------- 5 files changed, 449 insertions(+), 604 deletions(-) rename docs/gui/{workshop-job.rst => job-details.rst} (80%) delete mode 100644 docs/gui/workorder-details.rst create mode 100644 gui/job-details.lua delete mode 100644 gui/workorder-details.lua delete mode 100644 gui/workshop-job.lua diff --git a/docs/gui/workshop-job.rst b/docs/gui/job-details.rst similarity index 80% rename from docs/gui/workshop-job.rst rename to docs/gui/job-details.rst index 3caaeef591..34ac550599 100644 --- a/docs/gui/workshop-job.rst +++ b/docs/gui/job-details.rst @@ -1,24 +1,17 @@ -gui/workshop-job -================ +gui/job-details +=============== .. dfhack-tool:: - :summary: Adjust the input materials used for a job at a workshop. - :tags: unavailable fort inspection jobs + :summary: Adjust the input materials and traits used for a job or manager order. + :tags: fort inspection jobs workorders interface -This tool allows you to inspect or change the input reagents for the selected -workshop job (in :kbd:`q` mode). - -.. image:: /docs/images/workshop-job.png +This tool allows you to inspect or change the input reagents for the selected job. Pressing :kbd:`i` shows a dialog where you can select an item type from a list. -.. image:: /docs/images/workshop-job-item.png - Pressing :kbd:`m` (unless the item type does not allow a material) lets you choose a material. -.. image:: /docs/images/workshop-job-material.png - Since there are a lot more materials than item types, this dialog is more complex and uses a hierarchy of sub-menus. List choices that open a sub-menu are marked with an arrow on the left. @@ -48,4 +41,10 @@ Usage :: - gui/workshop-job + gui/job-details + +Overlay +------- + +The position of the "details" button that appears when a job details +window is open is configurable via `gui/overlay`. diff --git a/docs/gui/workorder-details.rst b/docs/gui/workorder-details.rst deleted file mode 100644 index 9cb3642981..0000000000 --- a/docs/gui/workorder-details.rst +++ /dev/null @@ -1,19 +0,0 @@ -gui/workorder-details -===================== - -.. dfhack-tool:: - :summary: Adjust input materials and traits for workorders. - :tags: unavailable fort inspection workorders - -This tool allows you to adjust item types, materials, and/or traits for items -used in manager workorders. The jobs created from those workorders will inherit -the details. - -Invoke while on a work order's detail screen (click on the magnifying glass). - -Usage ------ - -:: - - gui/workorder-details diff --git a/gui/job-details.lua b/gui/job-details.lua new file mode 100644 index 0000000000..5db27acca7 --- /dev/null +++ b/gui/job-details.lua @@ -0,0 +1,437 @@ +-- Show and modify properties of jobs in a workshop or of manager orders. + +--[[ + Credit goes to the author of `gui/workshop-job`. + This is the gui/workshop-job.lua with gui/workorder-details.lua added on top + and updated to the DF50. +]] + +--@ module = true + +local overlay = require('plugins.overlay') + +local utils = require 'utils' +local gui = require 'gui' +local guimat = require 'gui.materials' +local widgets = require 'gui.widgets' + +JobDetails = defclass(JobDetails, gui.ZScreenModal) + +JobDetails.focus_path = 'job-details' + +JobDetails.ATTRS { + job = DEFAULT_NIL, + context = DEFAULT_NIL, + frame_inset = 1, + frame_background = COLOR_BLACK, +} + +function JobDetails:isManagerOrder() + return self.context == df.job_details_context_type.MANAGER_WORK_ORDER +end + +function JobDetails:init(args) + local status + if not self:isManagerOrder() then + status = { text = 'No worker', pen = COLOR_DARKGREY } + local worker = dfhack.job.getWorker(self.job) + if self.job.flags.suspend then + status = { text = 'Suspended', pen = COLOR_RED } + elseif worker then + status = { text = dfhack.TranslateName(dfhack.units.getVisibleName(worker)), pen = COLOR_GREEN } + end + end + + local window = widgets.Window{ + frame_title='Details', + resizable = true, + resize_min={w=50, h=20}, + frame = { l = 10, w = 50 }, + } + window:addviews{ + widgets.Label{ + frame = { l = 0, t = 0 }, + text = { + { text = df.job_type.attrs[self.job.job_type].caption }, NEWLINE, NEWLINE, + ' ', status + } + }, + widgets.Label{ + frame = { l = 0, t = 4 }, + text = { + { key = 'CUSTOM_I', text = ': Input item, ', + enabled = self:callback('canChangeIType'), + on_activate = self:callback('onChangeIType') }, + { key = 'CUSTOM_M', text = ': Material, ', + enabled = self:callback('canChangeMat'), + on_activate = self:callback('onChangeMat') }, + { key = 'CUSTOM_T', text = ': Traits', + enabled = self:callback('canChangeTrait'), + on_activate = self:callback('onChangeTrait') } + } + }, + widgets.List{ + view_id = 'list', + frame = { t = 6, b = 2 }, + row_height = 4, + scroll_keys = widgets.SECONDSCROLL, + }, + widgets.Label{ + frame = { l = 0, b = 0 }, + text = { + { key = 'LEAVESCREEN', text = ': Back', + on_activate = self:callback('dismiss') + } + } + }, + } + + self:addviews{window} + self.list = window.subviews.list + + self:initListChoices() + + local h = 2 -- window border + + self.list.frame.t -- everything above the list + + 4 * #self.list.choices -- list body + + 2 -- LEAVESCREEN + + 2 -- window border + window.frame.h = h +end + +local function describe_item_type(iobj) + local itemline = 'any item' + if iobj.item_type >= 0 then + itemline = df.item_type.attrs[iobj.item_type].caption or iobj.item_type + local def = dfhack.items.getSubtypeDef(iobj.item_type, iobj.item_subtype) + local count = dfhack.items.getSubtypeCount(iobj.item_type, iobj.item_subtype) + if def then + itemline = def.name + elseif count >= 0 then + itemline = 'any '..itemline + end + end + return itemline +end + +local function is_caste_mat(iobj) + return dfhack.items.isCasteMaterial(iobj.item_type) +end + +local function describe_material(iobj) + local matline = 'any material' + if is_caste_mat(iobj) then + matline = 'material not applicable' + elseif iobj.mat_type >= 0 then + local info = dfhack.matinfo.decode(iobj.mat_type, iobj.mat_index) + if info then + matline = info:toString() + else + matline = iobj.mat_type..':'..iobj.mat_index + end + end + return matline +end + +local function list_flags(list, bitfield) + for name,val in pairs(bitfield) do + if val then + table.insert(list, name) + end + end +end + +local function describe_item_traits(iobj) + local line1 = {} + local reaction = df.reaction.find(iobj.reaction_id) + if reaction and #iobj.contains > 0 then + for _,ri in ipairs(iobj.contains) do + table.insert(line1, 'has '..utils.call_with_string( + reaction.reagents[ri],'getDescription',iobj.reaction_id + )) + end + end + if iobj.metal_ore >= 0 then + local ore = dfhack.matinfo.decode(0, iobj.metal_ore) + if ore then + table.insert(line1, 'ore of '..ore:toString()) + end + end + if iobj.has_material_reaction_product ~= '' then + table.insert(line1, iobj.has_material_reaction_product .. '-producing') + end + if iobj.reaction_class ~= '' then + table.insert(line1, 'reaction class '..iobj.reaction_class) + end + if iobj.has_tool_use >= 0 then + table.insert(line1, 'has use '..df.tool_uses[iobj.has_tool_use]) + end + + list_flags(line1, iobj.flags1) + list_flags(line1, iobj.flags2) + list_flags(line1, iobj.flags3) + + if #line1 == 0 then + table.insert(line1, 'no traits') + end + return table.concat(line1, ', ') +end + +function JobDetails:initListChoices() + local headers = {} + local job_items + if self:isManagerOrder() then + if not self.job.items then + self.list:setChoices({}) + return + end + + job_items = self.job.items + for i,iobj in ipairs(job_items) do + local head = 'Item '..(i+1)..' x'..iobj.quantity + if iobj.min_dimension > 0 then + head = head .. ' (size '..iobj.min_dimension..')' + end + + headers[i] = head + end + else + local items = {} + for i,ref in ipairs(self.job.items) do + local idx = ref.job_item_idx + if idx >= 0 then + items[idx] = (items[idx] or 0) + 1 + end + end + + job_items = self.job.job_items + for i,iobj in ipairs(job_items) do + local head = 'Item '..(i+1)..': '..(items[i] or 0)..' of '..iobj.quantity + if iobj.min_dimension > 0 then + head = head .. ' (size '..iobj.min_dimension..')' + end + + headers[i] = head + end + end + + local choices = {} + for i,iobj in ipairs(job_items) do + local head = headers[i] + + table.insert(choices, { + index = i, + iobj = iobj, + text = { + head, NEWLINE, + ' ', { text = curry(describe_item_type, iobj) }, NEWLINE, + ' ', { text = curry(describe_material, iobj) }, NEWLINE, + ' ', { text = curry(describe_item_traits, iobj) }, NEWLINE, + } + }) + end + + self.list:setChoices(choices) +end + +function JobDetails:canChangeIType() + local idx, obj = self.list:getSelected() + return obj ~= nil +end + +function JobDetails:setItemType(obj, item_type, item_subtype) + obj.iobj.item_type = item_type + obj.iobj.item_subtype = item_subtype + + if is_caste_mat(obj.iobj) then + self:setMaterial(obj, -1, -1) + end +end + +function JobDetails:onChangeIType() + local idx, obj = self.list:getSelected() + guimat.ItemTypeDialog{ + prompt = 'Please select a new item type for input '..idx, + none_caption = 'any item', + item_filter = curry(dfhack.job.isSuitableItem, obj.iobj), + on_select = self:callback('setItemType', obj) + }:show() +end + +function JobDetails:canChangeMat() + local idx, obj = self.list:getSelected() + return obj ~= nil and not is_caste_mat(obj.iobj) +end + +function JobDetails:setMaterial(obj, mat_type, mat_index) + if obj.index == 0 + and self.job.mat_type == obj.iobj.mat_type + and self.job.mat_index == obj.iobj.mat_index + and self.job.job_type ~= df.job_type.PrepareMeal + then + self.job.mat_type = mat_type + self.job.mat_index = mat_index + end + + obj.iobj.mat_type = mat_type + obj.iobj.mat_index = mat_index +end + +function JobDetails:findUnambiguousItem(iobj) + local count = 0 + local itype + + for i = 0,df.item_type._last_item do + if dfhack.job.isSuitableItem(iobj, i, -1) then + count = count + 1 + if count > 1 then return nil end + itype = i + end + end + + return itype +end + +function JobDetails:onChangeMat() + local idx, obj = self.list:getSelected() + + if obj.iobj.item_type == -1 and obj.iobj.mat_type == -1 then + -- If the job allows only one specific item type, use it + local vitype = self:findUnambiguousItem(obj.iobj) + + if vitype then + obj.iobj.item_type = vitype + else + dlg.showMessage( + 'Bug Alert', + { 'Please set a specific item type first.\n\n', + 'Otherwise the material will be matched\n', + 'incorrectly due to a limitation in DF code.' }, + COLOR_YELLOW + ) + return + end + end + + guimat.MaterialDialog{ + prompt = 'Please select a new material for input '..idx, + none_caption = 'any material', + mat_filter = function(mat,parent,mat_type,mat_index) + return dfhack.job.isSuitableMaterial(obj.iobj, mat_type, mat_index, obj.iobj.item_type) + end, + on_select = self:callback('setMaterial', obj) + }:show() +end + +function JobDetails:canChangeTrait() + local idx, obj = self.list:getSelected() + return obj ~= nil and not is_caste_mat(obj.iobj) +end + +function JobDetails:onChangeTrait() + local idx, obj = self.list:getSelected() + guimat.ItemTraitsDialog{ + job_item = obj.iobj, + prompt = 'Please select traits for input '..idx, + none_caption = 'no traits', + }:show() +end + +local function ScrJobDetails() + return df.global.game.main_interface.job_details +end + +local function show_job_details() + local scr = ScrJobDetails() + if not scr.open -- dfhack.gui.matchFocusString('dwarfmode/JobDetails') + then + qerror("This script needs to be run from a job details screen") + end + + local job + if scr.context == df.job_details_context_type.BUILDING_TASK_LIST then + job = scr.jb + elseif scr.context == df.job_details_context_type.MANAGER_WORK_ORDER then + job = scr.wq + end + if job == nil then + qerror("Unhandled screen context: ".. df.job_details_context_type[scr.context]) + end + + local dlg = JobDetails{ job = job, context = scr.context } + dlg:show() +end + +-- -------------------- +-- DetailsHotkeyOverlay +-- + +local focusStrings = 'dwarfmode/JobDetails' + +DetailsHotkeyOverlay = defclass(DetailsHotkeyOverlay, overlay.OverlayWidget) +DetailsHotkeyOverlay.ATTRS{ + default_pos={x=0,y=0}, + default_enabled=true, + viewscreens=focusStrings, + frame={w=1+6+2+(7)+1, h=3}, +} + +function DetailsHotkeyOverlay:init() + self:addviews{ + widgets.TextButton{ + view_id = 'button', + frame={t=0, l=0, r=0, h=1}, + label='details', + key='CUSTOM_CTRL_D', + on_activate=show_job_details, + }, + } +end + +local function isManagerOrderScreen() + local scr = ScrJobDetails() + return scr.context == df.job_details_context_type.MANAGER_WORK_ORDER +end + +DetailsHotkeyOverlay_ManagerWorkOrder = defclass(DetailsHotkeyOverlay_ManagerWorkOrder, DetailsHotkeyOverlay) +DetailsHotkeyOverlay_ManagerWorkOrder.ATTRS{ + default_pos={x=5, y=5}, -- {x=5, y=5} is right above the job title +} +function DetailsHotkeyOverlay_ManagerWorkOrder:onRenderBody(dc) + if isManagerOrderScreen() then + self.subviews.button.visible = true + else + self.subviews.button.visible = false + return + end + + DetailsHotkeyOverlay.super.onRenderBody(self, dc) +end + +DetailsHotkeyOverlay_BuildingTask = defclass(DetailsHotkeyOverlay_BuildingTask, DetailsHotkeyOverlay) +DetailsHotkeyOverlay_BuildingTask.ATTRS{ + default_pos={x=-123, y=6}, -- {x=-123, y=6} is right above the job title on all but smallest widths +} +function DetailsHotkeyOverlay_BuildingTask:onRenderBody(dc) + if not isManagerOrderScreen() then + self.subviews.button.visible = true + else + self.subviews.button.visible = false + return + end + + DetailsHotkeyOverlay.super.onRenderBody(self, dc) +end + +-- ------------------- + +OVERLAY_WIDGETS = { + job_details=DetailsHotkeyOverlay_BuildingTask, + workorder_details=DetailsHotkeyOverlay_ManagerWorkOrder, +} + +if dfhack_flags.module then + return +end + +show_job_details() \ No newline at end of file diff --git a/gui/workorder-details.lua b/gui/workorder-details.lua deleted file mode 100644 index 4c93828c12..0000000000 --- a/gui/workorder-details.lua +++ /dev/null @@ -1,234 +0,0 @@ --- adjust work orders' input item, material, traits - ---@ module = true - ---[[ -Credit goes to the author of `gui/workshop-job`, it showed -me the way. Also, a huge chunk of code could be reused. -]] - -local overlay = require('plugins.overlay') - -local utils = require 'utils' -local gui = require 'gui' -local guimat = require 'gui.materials' -local widgets = require 'gui.widgets' - -local wsj = reqscript 'gui/workshop-job' - -local JobDetails = defclass(JobDetails, gui.FramedScreen) - -JobDetails.focus_path = 'workorder-details' - -JobDetails.ATTRS { - job = DEFAULT_NIL, - frame_inset = 1, - frame_background = COLOR_BLACK, -} - -function JobDetails:init(args) - self:addviews{ - widgets.Label{ - frame = { l = 0, t = 0 }, - text = { - { text = df.job_type.attrs[self.job.job_type].caption }, NEWLINE, NEWLINE, - ' ', status - } - }, - widgets.Label{ - frame = { l = 0, t = 4 }, - text = { - { key = 'CUSTOM_I', text = ': Input item, ', - enabled = self:callback('canChangeIType'), - on_activate = self:callback('onChangeIType') }, - { key = 'CUSTOM_M', text = ': Material, ', - enabled = self:callback('canChangeMat'), - on_activate = self:callback('onChangeMat') }, - { key = 'CUSTOM_T', text = ': Traits', - enabled = self:callback('canChangeTrait'), - on_activate = self:callback('onChangeTrait') } - } - }, - widgets.List{ - view_id = 'list', - frame = { t = 6, b = 2 }, - row_height = 4, - }, - widgets.Label{ - frame = { l = 0, b = 0 }, - text = { - { key = 'LEAVESCREEN', text = ': Back', - on_activate = self:callback('dismiss') } - } - }, - } - - self:initListChoices() -end - -function JobDetails:onGetSelectedJob() - return self.job -end - -local describe_item_type = wsj.describe_item_type -local is_caste_mat = wsj.is_caste_mat -local describe_material = wsj.describe_material -local list_flags = wsj.list_flags - -local function describe_item_traits(iobj) - local line1 = {} - local reaction = df.reaction.find(iobj.reaction_id) - if reaction and #iobj.contains > 0 then - for _,ri in ipairs(iobj.contains) do - table.insert(line1, 'has '..utils.call_with_string( - reaction.reagents[ri],'getDescription',iobj.reaction_id - )) - end - end - if iobj.metal_ore >= 0 then - local ore = dfhack.matinfo.decode(0, iobj.metal_ore) - if ore then - table.insert(line1, 'ore of '..ore:toString()) - end - end - if iobj.has_material_reaction_product ~= '' then - table.insert(line1, iobj.has_material_reaction_product .. '-producing') - end - if iobj.reaction_class ~= '' then - table.insert(line1, 'reaction class '..iobj.reaction_class) - end - if iobj.has_tool_use >= 0 then - table.insert(line1, 'has use '..df.tool_uses[iobj.has_tool_use]) - end - - list_flags(line1, iobj.flags1) - list_flags(line1, iobj.flags2) - list_flags(line1, iobj.flags3) - - if #line1 == 0 then - table.insert(line1, 'no traits') - end - return table.concat(line1, ', ') -end - -function JobDetails:initListChoices() - if not self.job.items then - self.subviews.list:setChoices({}) - return - end - - local choices = {} - for i,iobj in ipairs(self.job.items) do - local head = 'Item '..(i+1)..' x'..iobj.quantity - if iobj.min_dimension > 0 then - head = head .. '(size '..iobj.min_dimension..')' - end - - table.insert(choices, { - index = i, - iobj = iobj, - text = { - head, NEWLINE, - ' ', { text = curry(describe_item_type, iobj) }, NEWLINE, - ' ', { text = curry(describe_material, iobj) }, NEWLINE, - ' ', { text = curry(describe_item_traits, iobj) }, NEWLINE - } - }) - end - - self.subviews.list:setChoices(choices) -end - -JobDetails.canChangeIType = wsj.JobDetails.canChangeIType -JobDetails.setItemType = wsj.JobDetails.setItemType -JobDetails.onChangeIType = wsj.JobDetails.onChangeIType -JobDetails.canChangeMat = wsj.JobDetails.canChangeMat -JobDetails.setMaterial = wsj.JobDetails.setMaterial -JobDetails.findUnambiguousItem = wsj.JobDetails.findUnambiguousItem -JobDetails.onChangeMat = wsj.JobDetails.onChangeMat - -function JobDetails:onInput(keys) - JobDetails.super.onInput(self, keys) -end - -function JobDetails:canChangeTrait() - local idx, obj = self.subviews.list:getSelected() - return obj ~= nil and not is_caste_mat(obj.iobj) -end - -function JobDetails:onChangeTrait() - local idx, obj = self.subviews.list:getSelected() - guimat.ItemTraitsDialog{ - job_item = obj.iobj, - prompt = 'Please select traits for input '..idx, - none_caption = 'no traits', - }:show() -end - -local function ScrJobDetails() - return df.global.game.main_interface.job_details -end - -local function show_job_details() - local scr = ScrJobDetails() - if not scr.open -- dfhack.gui.matchFocusString('dwarfmode/JobDetails') - or scr.context ~= df.job_details_context_type.MANAGER_WORK_ORDER - then - qerror("This script needs to be run from a work order details screen") - end - - -- by opening the viewscreen_workquota_detailsst the - -- work order's .items array is initialized - JobDetails{ job = scr.wq }:show() -end - --- ------------------- --- RecheckOverlay --- - -local focusStrings = 'dwarfmode/JobDetails' - -DetailsHotkeyOverlay = defclass(DetailsHotkeyOverlay, overlay.OverlayWidget) -DetailsHotkeyOverlay.ATTRS{ - default_pos={x=5,y=7}, - default_enabled=true, - viewscreens=focusStrings, - frame={w=1+6+2+(7)+1, h=3}, -} - -function DetailsHotkeyOverlay:init() - self:addviews{ - widgets.TextButton{ - view_id = 'button', - frame={t=0, l=0, r=0, h=1}, - label='details', - key='CUSTOM_CTRL_D', - on_activate=show_job_details, - }, - } -end - -function DetailsHotkeyOverlay:onRenderBody(dc) - local scr = ScrJobDetails() - - if (scr.context ~= df.job_details_context_type.MANAGER_WORK_ORDER) then - self.subviews.button.visible = false - return - else - self.subviews.button.visible = true - end - - DetailsHotkeyOverlay.super.onRenderBody(self, dc) -end - --- ------------------- - -OVERLAY_WIDGETS = { - details=DetailsHotkeyOverlay, -} - -if dfhack_flags.module then - return -end - -show_job_details() diff --git a/gui/workshop-job.lua b/gui/workshop-job.lua deleted file mode 100644 index 406c15ebb0..0000000000 --- a/gui/workshop-job.lua +++ /dev/null @@ -1,338 +0,0 @@ --- Show and modify properties of jobs in a workshop. ---[====[ - -gui/workshop-job -================ -Run with a job selected in a workshop in the :kbd:`q` mode. - -.. image:: /docs/images/workshop-job.png - -The script shows a list of the input reagents of the selected job, and allows changing -them like the `job` ``item-type`` and `job` ``item-material`` commands. - -Specifically, pressing the :kbd:`i` key pops up a dialog that lets you select an item -type from a list. - -.. image:: /docs/images/workshop-job-item.png - -Pressing :kbd:`m`, unless the item type does not allow a material, -lets you choose a material. - -.. image:: /docs/images/workshop-job-material.png - -Since there are a lot more materials than item types, this dialog is more complex -and uses a hierarchy of sub-menus. List choices that open a sub-menu are marked -with an arrow on the left. - -.. warning:: - - Due to the way input reagent matching works in DF, you must select an item type - if you select a material, or the material will be matched incorrectly in some cases. - If you press :kbd:`m` without choosing an item type, the script will auto-choose - if there is only one valid choice, or pop up an error message box instead of the - material selection dialog. - -Note that both materials and item types presented in the dialogs are filtered -by the job input flags, and even the selected item type for material selection, -or material for item type selection. Many jobs would let you select only one -input item type. - -For example, if you choose a *plant* input item type for your prepare meal job, -it will only let you select cookable materials. - -If you choose a *barrel* item instead (meaning things stored in barrels, like -drink or milk), it will let you select any material, since in this case the -material is matched against the barrel itself. Then, if you select, say, iron, -and then try to change the input item type, now it won't let you select *plant*; -you have to unset the material first. - -]====] - ---@ module = true - -local utils = require 'utils' -local gui = require 'gui' -local guidm = require 'gui.dwarfmode' -local guimat = require 'gui.materials' -local widgets = require 'gui.widgets' -local dlg = require 'gui.dialogs' - -JobDetails = defclass(JobDetails, guidm.MenuOverlay) - -JobDetails.focus_path = 'workshop-job' - -JobDetails.ATTRS { - job = DEFAULT_NIL, - frame_inset = 1, - frame_background = COLOR_BLACK, -} - -function JobDetails:init(args) - self.building = dfhack.job.getHolder(self.job) - - local status = { text = 'No worker', pen = COLOR_DARKGREY } - local worker = dfhack.job.getWorker(self.job) - if self.job.flags.suspend then - status = { text = 'Suspended', pen = COLOR_RED } - elseif worker then - status = { text = dfhack.TranslateName(dfhack.units.getVisibleName(worker)), pen = COLOR_GREEN } - end - - self:addviews{ - widgets.Label{ - frame = { l = 0, t = 0 }, - text = { - { text = df.job_type.attrs[self.job.job_type].caption }, NEWLINE, NEWLINE, - ' ', status - } - }, - widgets.Label{ - frame = { l = 0, t = 4 }, - text = { - { key = 'CUSTOM_I', text = ': Input item, ', - enabled = self:callback('canChangeIType'), - on_activate = self:callback('onChangeIType') }, - { key = 'CUSTOM_M', text = ': Material', - enabled = self:callback('canChangeMat'), - on_activate = self:callback('onChangeMat') } - } - }, - widgets.List{ - view_id = 'list', - frame = { t = 6, b = 2 }, - row_height = 4, - scroll_keys = widgets.SECONDSCROLL, - }, - widgets.Label{ - frame = { l = 0, b = 0 }, - text = { - { key = 'LEAVESCREEN', text = ': Back', - on_activate = self:callback('dismiss') } - } - }, - } - - self:initListChoices() -end - -function JobDetails:onGetSelectedBuilding() - return self.building -end - -function JobDetails:onGetSelectedJob() - return self.job -end - -function describe_item_type(iobj) - local itemline = 'any item' - if iobj.item_type >= 0 then - itemline = df.item_type.attrs[iobj.item_type].caption or iobj.item_type - local def = dfhack.items.getSubtypeDef(iobj.item_type, iobj.item_subtype) - local count = dfhack.items.getSubtypeCount(iobj.item_type, iobj.item_subtype) - if def then - itemline = def.name - elseif count >= 0 then - itemline = 'any '..itemline - end - end - return itemline -end - -function is_caste_mat(iobj) - return dfhack.items.isCasteMaterial(iobj.item_type) -end - -function describe_material(iobj) - local matline = 'any material' - if is_caste_mat(iobj) then - matline = 'material not applicable' - elseif iobj.mat_type >= 0 then - local info = dfhack.matinfo.decode(iobj.mat_type, iobj.mat_index) - if info then - matline = info:toString() - else - matline = iobj.mat_type..':'..iobj.mat_index - end - end - return matline -end - -function list_flags(list, bitfield) - for name,val in pairs(bitfield) do - if val then - table.insert(list, name) - end - end -end - -function JobDetails:initListChoices() - local items = {} - for i,ref in ipairs(self.job.items) do - local idx = ref.job_item_idx - if idx >= 0 then - items[idx] = (items[idx] or 0) + 1 - end - end - - local choices = {} - for i,iobj in ipairs(self.job.job_items) do - local head = 'Item '..(i+1)..': '..(items[i] or 0)..' of '..iobj.quantity - if iobj.min_dimension > 0 then - head = head .. '(size '..iobj.min_dimension..')' - end - - local line1 = {} - local reaction = df.reaction.find(iobj.reaction_id) - if reaction and #iobj.contains > 0 then - for _,ri in ipairs(iobj.contains) do - table.insert(line1, 'has '..utils.call_with_string( - reaction.reagents[ri],'getDescription',iobj.reaction_id - )) - end - end - if iobj.metal_ore >= 0 then - local ore = dfhack.matinfo.decode(0, iobj.metal_ore) - if ore then - table.insert(line1, 'ore of '..ore:toString()) - end - end - if iobj.has_material_reaction_product ~= '' then - table.insert(line1, 'product '..iobj.has_material_reaction_product) - end - if iobj.reaction_class ~= '' then - table.insert(line1, 'class '..iobj.reaction_class) - end - if iobj.has_tool_use >= 0 then - table.insert(line1, 'has use '..df.tool_uses[iobj.has_tool_use]) - end - list_flags(line1, iobj.flags1) - list_flags(line1, iobj.flags2) - list_flags(line1, iobj.flags3) - if #line1 == 0 then - table.insert(line1, 'no flags') - end - - table.insert(choices, { - index = i, - iobj = iobj, - text = { - head, NEWLINE, - ' ', { text = curry(describe_item_type, iobj) }, NEWLINE, - ' ', { text = curry(describe_material, iobj) }, NEWLINE, - ' ', table.concat(line1, ', '), NEWLINE - } - }) - end - - self.subviews.list:setChoices(choices) -end - -function JobDetails:canChangeIType() - local idx, obj = self.subviews.list:getSelected() - return obj ~= nil -end - -function JobDetails:setItemType(obj, item_type, item_subtype) - obj.iobj.item_type = item_type - obj.iobj.item_subtype = item_subtype - - if is_caste_mat(obj.iobj) then - self:setMaterial(obj, -1, -1) - end -end - -function JobDetails:onChangeIType() - local idx, obj = self.subviews.list:getSelected() - guimat.ItemTypeDialog{ - prompt = 'Please select a new item type for input '..idx, - none_caption = 'any item', - item_filter = curry(dfhack.job.isSuitableItem, obj.iobj), - on_select = self:callback('setItemType', obj) - }:show() -end - -function JobDetails:canChangeMat() - local idx, obj = self.subviews.list:getSelected() - return obj ~= nil and not is_caste_mat(obj.iobj) -end - -function JobDetails:setMaterial(obj, mat_type, mat_index) - if obj.index == 0 - and self.job.mat_type == obj.iobj.mat_type - and self.job.mat_index == obj.iobj.mat_index - and self.job.job_type ~= df.job_type.PrepareMeal - then - self.job.mat_type = mat_type - self.job.mat_index = mat_index - end - - obj.iobj.mat_type = mat_type - obj.iobj.mat_index = mat_index -end - -function JobDetails:findUnambiguousItem(iobj) - local count = 0 - local itype - - for i = 0,df.item_type._last_item do - if dfhack.job.isSuitableItem(iobj, i, -1) then - count = count + 1 - if count > 1 then return nil end - itype = i - end - end - - return itype -end - -function JobDetails:onChangeMat() - local idx, obj = self.subviews.list:getSelected() - - if obj.iobj.item_type == -1 and obj.iobj.mat_type == -1 then - -- If the job allows only one specific item type, use it - local vitype = self:findUnambiguousItem(obj.iobj) - - if vitype then - obj.iobj.item_type = vitype - else - dlg.showMessage( - 'Bug Alert', - { 'Please set a specific item type first.\n\n', - 'Otherwise the material will be matched\n', - 'incorrectly due to a limitation in DF code.' }, - COLOR_YELLOW - ) - return - end - end - - guimat.MaterialDialog{ - prompt = 'Please select a new material for input '..idx, - none_caption = 'any material', - mat_filter = function(mat,parent,mat_type,mat_index) - return dfhack.job.isSuitableMaterial(obj.iobj, mat_type, mat_index, obj.iobj.item_type) - end, - on_select = self:callback('setMaterial', obj) - }:show() -end - -function JobDetails:onInput(keys) - if self:propagateMoveKeys(keys) then - if df.global.world.selected_building ~= self.building then - self:dismiss() - end - else - JobDetails.super.onInput(self, keys) - end -end - -if dfhack_flags.module then - return -end - -if not string.match(dfhack.gui.getCurFocus(), '^dwarfmode/QueryBuilding/Some/Workshop/Job') then - qerror("This script requires a workshop job selected in the 'q' mode") -end - -local dlg = JobDetails{ job = dfhack.gui.getSelectedJob() } -dlg:show() From c94a5bee83db8e19cdbe976e3abf3a5657633205 Mon Sep 17 00:00:00 2001 From: Timur Kelman Date: Sun, 3 Sep 2023 19:26:12 +0200 Subject: [PATCH 05/31] Update job-details.lua: fix whitespaces --- gui/job-details.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gui/job-details.lua b/gui/job-details.lua index 5db27acca7..b516b4d3be 100644 --- a/gui/job-details.lua +++ b/gui/job-details.lua @@ -185,7 +185,7 @@ function JobDetails:initListChoices() self.list:setChoices({}) return end - + job_items = self.job.items for i,iobj in ipairs(job_items) do local head = 'Item '..(i+1)..' x'..iobj.quantity @@ -434,4 +434,4 @@ if dfhack_flags.module then return end -show_job_details() \ No newline at end of file +show_job_details() From 1f2c9f5f48d2d4e73334517b5b1728c307188921 Mon Sep 17 00:00:00 2001 From: Timur Kelman Date: Sun, 3 Sep 2023 19:56:49 +0200 Subject: [PATCH 06/31] `require 'gui.dialogs'` for the `showMessage()` --- gui/job-details.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/gui/job-details.lua b/gui/job-details.lua index b516b4d3be..6a95fe31d2 100644 --- a/gui/job-details.lua +++ b/gui/job-details.lua @@ -14,6 +14,7 @@ local utils = require 'utils' local gui = require 'gui' local guimat = require 'gui.materials' local widgets = require 'gui.widgets' +local dlg = require 'gui.dialogs' JobDetails = defclass(JobDetails, gui.ZScreenModal) From 3846f1f90cd5e7a2d2b1a99825881b12ef274812 Mon Sep 17 00:00:00 2001 From: Timur Kelman Date: Sun, 3 Sep 2023 20:09:12 +0200 Subject: [PATCH 07/31] add instructions how to open the tool --- docs/gui/job-details.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/gui/job-details.rst b/docs/gui/job-details.rst index 34ac550599..d7cddc7065 100644 --- a/docs/gui/job-details.rst +++ b/docs/gui/job-details.rst @@ -5,7 +5,8 @@ gui/job-details :summary: Adjust the input materials and traits used for a job or manager order. :tags: fort inspection jobs workorders interface -This tool allows you to inspect or change the input reagents for the selected job. +This tool allows you to inspect or change the input reagents for the selected job: +open the job's "Details" screen by clicking the magnifying glass, then press :kbd:`Ctrl-D`. Pressing :kbd:`i` shows a dialog where you can select an item type from a list. From d80af4f283d120c6ec64dfc15d60da638b3ad364 Mon Sep 17 00:00:00 2001 From: Timur Kelman Date: Mon, 2 Oct 2023 18:27:46 +0200 Subject: [PATCH 08/31] explain magic numbers --- gui/job-details.lua | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/gui/job-details.lua b/gui/job-details.lua index 6a95fe31d2..6d4fd6ea4d 100644 --- a/gui/job-details.lua +++ b/gui/job-details.lua @@ -374,7 +374,13 @@ DetailsHotkeyOverlay.ATTRS{ default_pos={x=0,y=0}, default_enabled=true, viewscreens=focusStrings, - frame={w=1+6+2+(7)+1, h=3}, + frame={w= 1 -- [ + + 6 -- Ctrl+d + + 2 -- :_ + + (7) -- details + + 1 -- ] + , h= 1 + }, } function DetailsHotkeyOverlay:init() From d06b5a8a06687db11fa35379ece0878f73d464a7 Mon Sep 17 00:00:00 2001 From: Timur Kelman Date: Mon, 2 Oct 2023 18:41:24 +0200 Subject: [PATCH 09/31] make use of more specific focus strings --- gui/job-details.lua | 29 +++-------------------------- 1 file changed, 3 insertions(+), 26 deletions(-) diff --git a/gui/job-details.lua b/gui/job-details.lua index 6d4fd6ea4d..55f24c88c6 100644 --- a/gui/job-details.lua +++ b/gui/job-details.lua @@ -395,40 +395,17 @@ function DetailsHotkeyOverlay:init() } end -local function isManagerOrderScreen() - local scr = ScrJobDetails() - return scr.context == df.job_details_context_type.MANAGER_WORK_ORDER -end - DetailsHotkeyOverlay_ManagerWorkOrder = defclass(DetailsHotkeyOverlay_ManagerWorkOrder, DetailsHotkeyOverlay) DetailsHotkeyOverlay_ManagerWorkOrder.ATTRS{ default_pos={x=5, y=5}, -- {x=5, y=5} is right above the job title + viewscreens='dwarfmode/JobDetails/MANAGER_WORK_ORDER', } -function DetailsHotkeyOverlay_ManagerWorkOrder:onRenderBody(dc) - if isManagerOrderScreen() then - self.subviews.button.visible = true - else - self.subviews.button.visible = false - return - end - - DetailsHotkeyOverlay.super.onRenderBody(self, dc) -end DetailsHotkeyOverlay_BuildingTask = defclass(DetailsHotkeyOverlay_BuildingTask, DetailsHotkeyOverlay) DetailsHotkeyOverlay_BuildingTask.ATTRS{ - default_pos={x=-123, y=6}, -- {x=-123, y=6} is right above the job title on all but smallest widths + default_pos={x=-120, y=6}, -- {x=-120, y=6} is right above the job title on all but smallest widths + viewscreens='dwarfmode/JobDetails/BUILDING_TASK_LIST', } -function DetailsHotkeyOverlay_BuildingTask:onRenderBody(dc) - if not isManagerOrderScreen() then - self.subviews.button.visible = true - else - self.subviews.button.visible = false - return - end - - DetailsHotkeyOverlay.super.onRenderBody(self, dc) -end -- ------------------- From 937e6d27479103a383db10a58ec294e19f11b55e Mon Sep 17 00:00:00 2001 From: Timur Kelman Date: Mon, 2 Oct 2023 18:43:37 +0200 Subject: [PATCH 10/31] avoid redefining a variable --- gui/job-details.lua | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/gui/job-details.lua b/gui/job-details.lua index 55f24c88c6..055b245ed6 100644 --- a/gui/job-details.lua +++ b/gui/job-details.lua @@ -359,8 +359,7 @@ local function show_job_details() qerror("Unhandled screen context: ".. df.job_details_context_type[scr.context]) end - local dlg = JobDetails{ job = job, context = scr.context } - dlg:show() + JobDetails{ job = job, context = scr.context }:show() end -- -------------------- From e2bd30fa848789a1b580555c5983937cde42ed42 Mon Sep 17 00:00:00 2001 From: Timur Kelman Date: Sun, 8 Oct 2023 14:47:31 +0200 Subject: [PATCH 11/31] use more descriptive label text --- gui/job-details.lua | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/gui/job-details.lua b/gui/job-details.lua index 055b245ed6..3a4624d66a 100644 --- a/gui/job-details.lua +++ b/gui/job-details.lua @@ -366,17 +366,18 @@ end -- DetailsHotkeyOverlay -- -local focusStrings = 'dwarfmode/JobDetails' +local LABEL_TEXT = 'Configure job inputs' +local LABEL_TEXT_LENGTH = string.len( LABEL_TEXT ) DetailsHotkeyOverlay = defclass(DetailsHotkeyOverlay, overlay.OverlayWidget) DetailsHotkeyOverlay.ATTRS{ default_pos={x=0,y=0}, default_enabled=true, - viewscreens=focusStrings, + viewscreens="override this in a subclass", frame={w= 1 -- [ + 6 -- Ctrl+d + 2 -- :_ - + (7) -- details + + LABEL_TEXT_LENGTH -- LABEL_TEXT + 1 -- ] , h= 1 }, @@ -387,7 +388,7 @@ function DetailsHotkeyOverlay:init() widgets.TextButton{ view_id = 'button', frame={t=0, l=0, r=0, h=1}, - label='details', + label=LABEL_TEXT, key='CUSTOM_CTRL_D', on_activate=show_job_details, }, From 07a9ccda4c06f354085c33062c090209c2d3d4ac Mon Sep 17 00:00:00 2001 From: Timur Kelman Date: Tue, 10 Oct 2023 21:35:00 +0200 Subject: [PATCH 12/31] separate ZScreenModal and Window --- gui/job-details.lua | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/gui/job-details.lua b/gui/job-details.lua index 3a4624d66a..946180933f 100644 --- a/gui/job-details.lua +++ b/gui/job-details.lua @@ -16,15 +16,26 @@ local guimat = require 'gui.materials' local widgets = require 'gui.widgets' local dlg = require 'gui.dialogs' -JobDetails = defclass(JobDetails, gui.ZScreenModal) +JobDetailsScreen = defclass(JobDetailsScreen, gui.ZScreenModal) -JobDetails.focus_path = 'job-details' +JobDetailsScreen.ATTRS { + focus_path = 'job-details', +} + +function JobDetailsScreen:init(args) + self:addviews{JobDetails(args)} +end + +JobDetails = defclass(JobDetails, widgets.Window) JobDetails.ATTRS { + frame_title='Details', + resizable = true, + resize_min={w=50, h=20}, + frame = { l = 10, w = 50 }, + -- job = DEFAULT_NIL, context = DEFAULT_NIL, - frame_inset = 1, - frame_background = COLOR_BLACK, } function JobDetails:isManagerOrder() @@ -359,7 +370,7 @@ local function show_job_details() qerror("Unhandled screen context: ".. df.job_details_context_type[scr.context]) end - JobDetails{ job = job, context = scr.context }:show() + JobDetailsScreen{ job = job, context = scr.context }:show() end -- -------------------- From bfa2892d6c26fd47333d1d64dd43f4a769b328e3 Mon Sep 17 00:00:00 2001 From: Timur Kelman Date: Tue, 10 Oct 2023 21:37:03 +0200 Subject: [PATCH 13/31] separate ZScreenModal and Window, part 2 --- gui/job-details.lua | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/gui/job-details.lua b/gui/job-details.lua index 946180933f..c4e6765ee3 100644 --- a/gui/job-details.lua +++ b/gui/job-details.lua @@ -54,13 +54,7 @@ function JobDetails:init(args) end end - local window = widgets.Window{ - frame_title='Details', - resizable = true, - resize_min={w=50, h=20}, - frame = { l = 10, w = 50 }, - } - window:addviews{ + self:addviews{ widgets.Label{ frame = { l = 0, t = 0 }, text = { @@ -92,14 +86,13 @@ function JobDetails:init(args) frame = { l = 0, b = 0 }, text = { { key = 'LEAVESCREEN', text = ': Back', - on_activate = self:callback('dismiss') + -- on_activate = self:callback('dismiss') } } }, } - self:addviews{window} - self.list = window.subviews.list + self.list = self.subviews.list self:initListChoices() @@ -108,7 +101,7 @@ function JobDetails:init(args) + 4 * #self.list.choices -- list body + 2 -- LEAVESCREEN + 2 -- window border - window.frame.h = h + self.frame.h = h end local function describe_item_type(iobj) From 590dd4a1acadd468a9a5825be2df7970cfc725c4 Mon Sep 17 00:00:00 2001 From: Timur Kelman Date: Sun, 5 Nov 2023 17:31:11 +0100 Subject: [PATCH 14/31] add the widget to workorder conditions as well --- gui/job-details.lua | 102 +++++++++++++++++++++++++++++++++++++------- 1 file changed, 86 insertions(+), 16 deletions(-) diff --git a/gui/job-details.lua b/gui/job-details.lua index c4e6765ee3..43f6891305 100644 --- a/gui/job-details.lua +++ b/gui/job-details.lua @@ -346,24 +346,42 @@ local function ScrJobDetails() return df.global.game.main_interface.job_details end +local function ScrWorkorderConditions() + return df.global.game.main_interface.info.work_orders.conditions +end + local function show_job_details() + local job + local context local scr = ScrJobDetails() - if not scr.open -- dfhack.gui.matchFocusString('dwarfmode/JobDetails') + + if scr.open then - qerror("This script needs to be run from a job details screen") + context = scr.context + if context == df.job_details_context_type.BUILDING_TASK_LIST + or context == df.job_details_context_type.TASK_LIST_TASK + then + job = scr.jb + elseif context == df.job_details_context_type.MANAGER_WORK_ORDER then + job = scr.wq + end + if job == nil then + qerror("Unhandled screen context: ".. df.job_details_context_type[context]) + end + else + scr = ScrWorkorderConditions() + if scr.open + then + context = df.job_details_context_type.MANAGER_WORK_ORDER + job = scr.wq + end end - local job - if scr.context == df.job_details_context_type.BUILDING_TASK_LIST then - job = scr.jb - elseif scr.context == df.job_details_context_type.MANAGER_WORK_ORDER then - job = scr.wq - end - if job == nil then - qerror("Unhandled screen context: ".. df.job_details_context_type[scr.context]) + if (job == nil) then + qerror("This script needs to be run from a job details or order conditions screen") end - JobDetailsScreen{ job = job, context = scr.context }:show() + JobDetailsScreen{ job = job, context = context }:show() end -- -------------------- @@ -399,23 +417,75 @@ function DetailsHotkeyOverlay:init() } end +DetailsHotkeyOverlay_BuildingTask = defclass(DetailsHotkeyOverlay_BuildingTask, DetailsHotkeyOverlay) +DetailsHotkeyOverlay_BuildingTask.ATTRS{ + default_pos={x=-120, y=6}, -- {x=-120, y=6} is right above the job title on all but smallest widths + viewscreens='dwarfmode/JobDetails/BUILDING_TASK_LIST', +} + DetailsHotkeyOverlay_ManagerWorkOrder = defclass(DetailsHotkeyOverlay_ManagerWorkOrder, DetailsHotkeyOverlay) DetailsHotkeyOverlay_ManagerWorkOrder.ATTRS{ default_pos={x=5, y=5}, -- {x=5, y=5} is right above the job title - viewscreens='dwarfmode/JobDetails/MANAGER_WORK_ORDER', + viewscreens={ + 'dwarfmode/JobDetails/MANAGER_WORK_ORDER', + -- as of DF50.11, once input materials in the task list are changed, + -- there is no going back (the magnifying glass button disappears), + -- which is why this option is disabled. + -- 'dwarfmode/JobDetails/TASK_LIST_TASK', + }, } -DetailsHotkeyOverlay_BuildingTask = defclass(DetailsHotkeyOverlay_BuildingTask, DetailsHotkeyOverlay) -DetailsHotkeyOverlay_BuildingTask.ATTRS{ - default_pos={x=-120, y=6}, -- {x=-120, y=6} is right above the job title on all but smallest widths - viewscreens='dwarfmode/JobDetails/BUILDING_TASK_LIST', +DetailsHotkeyOverlay_ManagerWorkOrderConditions = defclass(DetailsHotkeyOverlay_ManagerWorkOrderConditions, DetailsHotkeyOverlay) +DetailsHotkeyOverlay_ManagerWorkOrderConditions.ATTRS{ + default_pos={x=37, y=7}, + frame={w=DetailsHotkeyOverlay.ATTRS.frame.w, h=3}, -- we need h=3 here to move the button around depending on tabs in one or two rows + viewscreens='dwarfmode/Info/WORK_ORDERS/Conditions', } +-- +-- change label position if window is resized +-- same logic as in workorder-recheck.lua +-- +local function areTabsInTwoRows() + -- get the tile above the order status icon + local pen = dfhack.screen.readTile(7, 7, false) + -- in graphics mode, `0` when one row, something else when two (`67` aka 'C' from "Creatures") + -- in ASCII mode, `32` aka ' ' when one row, something else when two (`196` aka '-' from tab frame's top) + return pen.ch == 67 or pen.ch == 196 +end + +function DetailsHotkeyOverlay_ManagerWorkOrderConditions:updateTextButtonFrame() + local twoRows = areTabsInTwoRows() + if (self._twoRows == twoRows) then return false end + + self._twoRows = twoRows + local frame = twoRows + and {b=0, l=0, r=0, h=1} + or {t=0, l=0, r=0, h=1} + self.subviews.button.frame = frame + + return true +end + +function DetailsHotkeyOverlay_ManagerWorkOrderConditions:onRenderBody(dc) + if (self.frame_rect.y1 == 7) then + -- only apply this logic if the overlay is on the same row as + -- originally thought: just above the order status icon + + if self:updateTextButtonFrame() then + self:updateLayout() + end + end + + DetailsHotkeyOverlay_ManagerWorkOrderConditions.super.onRenderBody(self, dc) +end + -- ------------------- OVERLAY_WIDGETS = { job_details=DetailsHotkeyOverlay_BuildingTask, workorder_details=DetailsHotkeyOverlay_ManagerWorkOrder, + workorder_conditions=DetailsHotkeyOverlay_ManagerWorkOrderConditions, } if dfhack_flags.module then From 760324989888a76323e564fb8825879a82e44aa4 Mon Sep 17 00:00:00 2001 From: Timur Kelman Date: Sun, 5 Nov 2023 17:44:01 +0100 Subject: [PATCH 15/31] use HotkeyLabel to make actions clickable --- gui/job-details.lua | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/gui/job-details.lua b/gui/job-details.lua index 43f6891305..926f5dcad3 100644 --- a/gui/job-details.lua +++ b/gui/job-details.lua @@ -62,19 +62,26 @@ function JobDetails:init(args) ' ', status } }, - widgets.Label{ - frame = { l = 0, t = 4 }, - text = { - { key = 'CUSTOM_I', text = ': Input item, ', - enabled = self:callback('canChangeIType'), - on_activate = self:callback('onChangeIType') }, - { key = 'CUSTOM_M', text = ': Material, ', - enabled = self:callback('canChangeMat'), - on_activate = self:callback('onChangeMat') }, - { key = 'CUSTOM_T', text = ': Traits', - enabled = self:callback('canChangeTrait'), - on_activate = self:callback('onChangeTrait') } - } + widgets.HotkeyLabel{ + frame = { l = 0, t = 4}, + key = 'CUSTOM_I', + label = "Input item", + enabled = self:callback('canChangeIType'), + on_activate = self:callback('onChangeIType'), + }, + widgets.HotkeyLabel{ + frame = { l = string.len("i: Input item") + 1, t = 4}, + key = 'CUSTOM_M', + label = "Material", + enabled = self:callback('canChangeMat'), + on_activate = self:callback('onChangeMat'), + }, + widgets.HotkeyLabel{ + frame = { l = string.len("i: Input item m: Material") + 1, t = 4}, + key = 'CUSTOM_T', + label = "Traits", + enabled = self:callback('canChangeTrait'), + on_activate = self:callback('onChangeTrait'), }, widgets.List{ view_id = 'list', From 9ada99a69709f7fdc5be7f2b46bf5dfdb022f282 Mon Sep 17 00:00:00 2001 From: Timur Kelman Date: Sun, 5 Nov 2023 20:11:34 +0100 Subject: [PATCH 16/31] place the overlay text over task name in BuildingTask view regardless of window width --- gui/job-details.lua | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/gui/job-details.lua b/gui/job-details.lua index 926f5dcad3..863eb60eee 100644 --- a/gui/job-details.lua +++ b/gui/job-details.lua @@ -416,7 +416,7 @@ function DetailsHotkeyOverlay:init() self:addviews{ widgets.TextButton{ view_id = 'button', - frame={t=0, l=0, r=0, h=1}, + frame={t=0, l=0, w=DetailsHotkeyOverlay.ATTRS.frame.w, h=1}, label=LABEL_TEXT, key='CUSTOM_CTRL_D', on_activate=show_job_details, @@ -426,10 +426,38 @@ end DetailsHotkeyOverlay_BuildingTask = defclass(DetailsHotkeyOverlay_BuildingTask, DetailsHotkeyOverlay) DetailsHotkeyOverlay_BuildingTask.ATTRS{ - default_pos={x=-120, y=6}, -- {x=-120, y=6} is right above the job title on all but smallest widths + default_pos={x=7, y=6}, + frame={w=1000, h= 1}, -- we'll move the text inside the line, that's why it's w=1000 viewscreens='dwarfmode/JobDetails/BUILDING_TASK_LIST', } +function DetailsHotkeyOverlay_BuildingTask:updateTextButtonFrame() + local mainWidth, _ = dfhack.screen.getWindowSize() + if (self._mainWidth == mainWidth) then return false end + + self._mainWidth = mainWidth + + local offset = 0 + local threshold = 145 + if mainWidth < threshold then + mainWidth = threshold + end + offset = (mainWidth - threshold) + + local frame = {l=6 + offset, w=DetailsHotkeyOverlay.ATTRS.frame.w} + self.subviews.button.frame = frame + + return true +end + +function DetailsHotkeyOverlay_BuildingTask:onRenderBody(dc) + if self:updateTextButtonFrame() then + self:updateLayout() + end + + DetailsHotkeyOverlay_BuildingTask.super.onRenderBody(self, dc) +end + DetailsHotkeyOverlay_ManagerWorkOrder = defclass(DetailsHotkeyOverlay_ManagerWorkOrder, DetailsHotkeyOverlay) DetailsHotkeyOverlay_ManagerWorkOrder.ATTRS{ default_pos={x=5, y=5}, -- {x=5, y=5} is right above the job title From ef0ebcc46dfcf5793a61c85cab7b9403316db4b8 Mon Sep 17 00:00:00 2001 From: Timur Kelman Date: Sun, 5 Nov 2023 20:42:12 +0100 Subject: [PATCH 17/31] do not show unnamed flags even if set --- gui/job-details.lua | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/gui/job-details.lua b/gui/job-details.lua index 863eb60eee..149807a5c4 100644 --- a/gui/job-details.lua +++ b/gui/job-details.lua @@ -145,10 +145,20 @@ local function describe_material(iobj) return matline end +local function isString(o) + return type(o) == "string" +end + local function list_flags(list, bitfield) for name,val in pairs(bitfield) do if val then - table.insert(list, name) + -- as of DFHack version 50.11-r2 (git: 94d70e0) on x86_64, + -- a job_item_flags3[20] might be set on a job item (f.e. Cut Gems) + -- even though the flag is unnamed (i.e. `df.job_item_flags3[20] == nil`) + -- we'll ignore those for clarity. + if isString(name) then + table.insert(list, name) + end end end end From bf055ab60c1bb44abfeed04e4d7722c5a15bf733 Mon Sep 17 00:00:00 2001 From: Timur Kelman Date: Sun, 5 Nov 2023 20:51:20 +0100 Subject: [PATCH 18/31] job-details.rst: mention where can we find the tool --- docs/gui/job-details.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/gui/job-details.rst b/docs/gui/job-details.rst index d7cddc7065..5b08ee71a7 100644 --- a/docs/gui/job-details.rst +++ b/docs/gui/job-details.rst @@ -6,7 +6,10 @@ gui/job-details :tags: fort inspection jobs workorders interface This tool allows you to inspect or change the input reagents for the selected job: -open the job's "Details" screen by clicking the magnifying glass, then press :kbd:`Ctrl-D`. +open the job's "Details" screen by clicking the magnifying glass, then press :kbd:`Ctrl-D` +or click the "Configure job inputs" label. This is possible in a workshop +job list and manager work order list (in case the magnifying glass is present), +as well as from the work order conditions screen. Pressing :kbd:`i` shows a dialog where you can select an item type from a list. From 85b92c060876c4bb4c01b36f559b39a194783269 Mon Sep 17 00:00:00 2001 From: Timur Kelman Date: Sun, 5 Nov 2023 20:57:26 +0100 Subject: [PATCH 19/31] enable in the workshops' work order details screen --- gui/job-details.lua | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/gui/job-details.lua b/gui/job-details.lua index 149807a5c4..05739c82a5 100644 --- a/gui/job-details.lua +++ b/gui/job-details.lua @@ -40,6 +40,7 @@ JobDetails.ATTRS { function JobDetails:isManagerOrder() return self.context == df.job_details_context_type.MANAGER_WORK_ORDER + or self.context == df.job_details_context_type.BUILDING_WORK_ORDER end function JobDetails:init(args) @@ -379,7 +380,9 @@ local function show_job_details() or context == df.job_details_context_type.TASK_LIST_TASK then job = scr.jb - elseif context == df.job_details_context_type.MANAGER_WORK_ORDER then + elseif context == df.job_details_context_type.MANAGER_WORK_ORDER + or context == df.job_details_context_type.BUILDING_WORK_ORDER + then job = scr.wq end if job == nil then @@ -438,7 +441,10 @@ DetailsHotkeyOverlay_BuildingTask = defclass(DetailsHotkeyOverlay_BuildingTask, DetailsHotkeyOverlay_BuildingTask.ATTRS{ default_pos={x=7, y=6}, frame={w=1000, h= 1}, -- we'll move the text inside the line, that's why it's w=1000 - viewscreens='dwarfmode/JobDetails/BUILDING_TASK_LIST', + viewscreens={ + 'dwarfmode/JobDetails/BUILDING_TASK_LIST', + 'dwarfmode/JobDetails/BUILDING_WORK_ORDER', + } } function DetailsHotkeyOverlay_BuildingTask:updateTextButtonFrame() From 36985edee5e5d37127641a3856ad706ebe20d48a Mon Sep 17 00:00:00 2001 From: Timur Kelman Date: Mon, 6 Nov 2023 21:20:18 +0100 Subject: [PATCH 20/31] improve positioning logic for building overlay --- gui/job-details.lua | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/gui/job-details.lua b/gui/job-details.lua index 05739c82a5..49ede0b5d6 100644 --- a/gui/job-details.lua +++ b/gui/job-details.lua @@ -439,8 +439,10 @@ end DetailsHotkeyOverlay_BuildingTask = defclass(DetailsHotkeyOverlay_BuildingTask, DetailsHotkeyOverlay) DetailsHotkeyOverlay_BuildingTask.ATTRS{ - default_pos={x=7, y=6}, - frame={w=1000, h= 1}, -- we'll move the text inside the line, that's why it's w=1000 + -- 7 is the x position of the text on the narrowest screen + -- we make the frame wider by 7 so we can move the label a bit if necessary + default_pos={x=-110 + 7, y=6}, + frame={w=DetailsHotkeyOverlay.ATTRS.frame.w + 7, h= 1}, viewscreens={ 'dwarfmode/JobDetails/BUILDING_TASK_LIST', 'dwarfmode/JobDetails/BUILDING_WORK_ORDER', @@ -453,15 +455,22 @@ function DetailsHotkeyOverlay_BuildingTask:updateTextButtonFrame() self._mainWidth = mainWidth + -- calculated position of the left edge - not necessarily the real one if the screen is too narrow + local x1 = mainWidth + DetailsHotkeyOverlay_BuildingTask.ATTRS.default_pos.x - DetailsHotkeyOverlay_BuildingTask.ATTRS.frame.w + local offset = 0 - local threshold = 145 - if mainWidth < threshold then - mainWidth = threshold + if x1 < 0 then + x1 = 0 + end + if x1 < 6 then + offset = 6 - x1 end - offset = (mainWidth - threshold) - local frame = {l=6 + offset, w=DetailsHotkeyOverlay.ATTRS.frame.w} - self.subviews.button.frame = frame + self.subviews.button.frame.l = offset + + -- this restores original position for the case the screen was narrowed to the minimum + -- and then expanded again. + self.frame.r = - DetailsHotkeyOverlay_BuildingTask.ATTRS.default_pos.x - 1 return true end From 069fd09d2ffb2606a13c7895ec499102367701db Mon Sep 17 00:00:00 2001 From: Timur Kelman Date: Mon, 6 Nov 2023 21:35:03 +0100 Subject: [PATCH 21/31] set auto_width=true on hotkey labels --- gui/job-details.lua | 3 +++ 1 file changed, 3 insertions(+) diff --git a/gui/job-details.lua b/gui/job-details.lua index 49ede0b5d6..d44f2456ed 100644 --- a/gui/job-details.lua +++ b/gui/job-details.lua @@ -67,6 +67,7 @@ function JobDetails:init(args) frame = { l = 0, t = 4}, key = 'CUSTOM_I', label = "Input item", + auto_width=true, enabled = self:callback('canChangeIType'), on_activate = self:callback('onChangeIType'), }, @@ -74,6 +75,7 @@ function JobDetails:init(args) frame = { l = string.len("i: Input item") + 1, t = 4}, key = 'CUSTOM_M', label = "Material", + auto_width=true, enabled = self:callback('canChangeMat'), on_activate = self:callback('onChangeMat'), }, @@ -81,6 +83,7 @@ function JobDetails:init(args) frame = { l = string.len("i: Input item m: Material") + 1, t = 4}, key = 'CUSTOM_T', label = "Traits", + auto_width=true, enabled = self:callback('canChangeTrait'), on_activate = self:callback('onChangeTrait'), }, From bd88a1a681f1831f68964f289d80c00416c8fd0a Mon Sep 17 00:00:00 2001 From: Timur Kelman Date: Tue, 7 Nov 2023 17:44:45 +0100 Subject: [PATCH 22/31] remove the 'LEAVESCREEN' label --- gui/job-details.lua | 8 -------- 1 file changed, 8 deletions(-) diff --git a/gui/job-details.lua b/gui/job-details.lua index d44f2456ed..cdad1a561a 100644 --- a/gui/job-details.lua +++ b/gui/job-details.lua @@ -93,14 +93,6 @@ function JobDetails:init(args) row_height = 4, scroll_keys = widgets.SECONDSCROLL, }, - widgets.Label{ - frame = { l = 0, b = 0 }, - text = { - { key = 'LEAVESCREEN', text = ': Back', - -- on_activate = self:callback('dismiss') - } - } - }, } self.list = self.subviews.list From 77ab5b981bae4964de14a0addd5fd8bcfde033e5 Mon Sep 17 00:00:00 2001 From: Timur Kelman Date: Tue, 7 Nov 2023 17:48:09 +0100 Subject: [PATCH 23/31] only hide numeric flag names if `dfhack.getHideArmokTools() == true` cache references to global statics --- gui/job-details.lua | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/gui/job-details.lua b/gui/job-details.lua index cdad1a561a..dc08b68ed7 100644 --- a/gui/job-details.lua +++ b/gui/job-details.lua @@ -152,7 +152,7 @@ local function list_flags(list, bitfield) -- a job_item_flags3[20] might be set on a job item (f.e. Cut Gems) -- even though the flag is unnamed (i.e. `df.job_item_flags3[20] == nil`) -- we'll ignore those for clarity. - if isString(name) then + if not dfhack.getHideArmokTools() or isString(name) then table.insert(list, name) end end @@ -355,18 +355,13 @@ function JobDetails:onChangeTrait() }:show() end -local function ScrJobDetails() - return df.global.game.main_interface.job_details -end - -local function ScrWorkorderConditions() - return df.global.game.main_interface.info.work_orders.conditions -end +local ScrJobDetails = df.global.game.main_interface.job_details +local ScrWorkorderConditions = df.global.game.main_interface.info.work_orders.conditions local function show_job_details() local job local context - local scr = ScrJobDetails() + local scr = ScrJobDetails if scr.open then @@ -384,7 +379,7 @@ local function show_job_details() qerror("Unhandled screen context: ".. df.job_details_context_type[context]) end else - scr = ScrWorkorderConditions() + scr = ScrWorkorderConditions if scr.open then context = df.job_details_context_type.MANAGER_WORK_ORDER From 597c48a91a8229e649d69364485d76e4296ff2df Mon Sep 17 00:00:00 2001 From: Timur Kelman Date: Tue, 7 Nov 2023 17:49:29 +0100 Subject: [PATCH 24/31] Use user-friendly quantity instead of internal counter Also, refactor the way header text is built --- gui/job-details.lua | 50 ++++++++++++++++++++++++++++++--------------- 1 file changed, 33 insertions(+), 17 deletions(-) diff --git a/gui/job-details.lua b/gui/job-details.lua index dc08b68ed7..340469a58f 100644 --- a/gui/job-details.lua +++ b/gui/job-details.lua @@ -195,9 +195,35 @@ local function describe_item_traits(iobj) return table.concat(line1, ', ') end +local function GetHeader(iobj, items, i, is_active_job) + local q = iobj.quantity + if iobj.min_dimension > 0 then + local q1 = q / iobj.min_dimension + q = math.floor(q1) -- this makes it an int, removing `.0` from `1.0` when converted to string + if q1 ~= q then + -- round to 1 decimal point + q = math.floor(q1 * 10) / 10 + end + end + + local head = 'Item '..(i+1) + if is_active_job then + head = head..': '..(items[i] or 0)..' of '..q + else + head = head..' (quantity: '..q..')' + end + + -- if iobj.min_dimension > 0 then + -- head = head .. ' (size '..iobj.min_dimension..')' + -- end + + return head +end + function JobDetails:initListChoices() - local headers = {} local job_items + local items = {} + local is_active_job = false if self:isManagerOrder() then if not self.job.items then self.list:setChoices({}) @@ -205,16 +231,9 @@ function JobDetails:initListChoices() end job_items = self.job.items - for i,iobj in ipairs(job_items) do - local head = 'Item '..(i+1)..' x'..iobj.quantity - if iobj.min_dimension > 0 then - head = head .. ' (size '..iobj.min_dimension..')' - end - - headers[i] = head - end else - local items = {} + is_active_job = true + for i,ref in ipairs(self.job.items) do local idx = ref.job_item_idx if idx >= 0 then @@ -223,14 +242,11 @@ function JobDetails:initListChoices() end job_items = self.job.job_items - for i,iobj in ipairs(job_items) do - local head = 'Item '..(i+1)..': '..(items[i] or 0)..' of '..iobj.quantity - if iobj.min_dimension > 0 then - head = head .. ' (size '..iobj.min_dimension..')' - end + end - headers[i] = head - end + local headers = {} + for i,iobj in ipairs(job_items) do + headers[i] = GetHeader(iobj, items, i, is_active_job) end local choices = {} From cd7ca2fc1ce287d239371262a174c6110d308132 Mon Sep 17 00:00:00 2001 From: Timur Kelman Date: Tue, 7 Nov 2023 17:55:28 +0100 Subject: [PATCH 25/31] use window size do determine if tabs are in two rows --- gui/job-details.lua | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/gui/job-details.lua b/gui/job-details.lua index 340469a58f..4e3f8966a3 100644 --- a/gui/job-details.lua +++ b/gui/job-details.lua @@ -510,14 +510,10 @@ DetailsHotkeyOverlay_ManagerWorkOrderConditions.ATTRS{ -- -- change label position if window is resized --- same logic as in workorder-recheck.lua -- local function areTabsInTwoRows() - -- get the tile above the order status icon - local pen = dfhack.screen.readTile(7, 7, false) - -- in graphics mode, `0` when one row, something else when two (`67` aka 'C' from "Creatures") - -- in ASCII mode, `32` aka ' ' when one row, something else when two (`196` aka '-' from tab frame's top) - return pen.ch == 67 or pen.ch == 196 + local mainWidth, _ = dfhack.screen.getWindowSize() + return mainWidth < 155 end function DetailsHotkeyOverlay_ManagerWorkOrderConditions:updateTextButtonFrame() From bb95a287dd24512b53d32543214a4f1427cad7e8 Mon Sep 17 00:00:00 2001 From: Timur Kelman Date: Tue, 7 Nov 2023 18:12:16 +0100 Subject: [PATCH 26/31] disable the key if job has no items --- gui/job-details.lua | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/gui/job-details.lua b/gui/job-details.lua index 4e3f8966a3..b888d6fa15 100644 --- a/gui/job-details.lua +++ b/gui/job-details.lua @@ -221,16 +221,14 @@ local function GetHeader(iobj, items, i, is_active_job) end function JobDetails:initListChoices() - local job_items + local job_items = self.job.items local items = {} local is_active_job = false if self:isManagerOrder() then - if not self.job.items then + if not job_items then self.list:setChoices({}) return end - - job_items = self.job.items else is_active_job = true @@ -240,8 +238,6 @@ function JobDetails:initListChoices() items[idx] = (items[idx] or 0) + 1 end end - - job_items = self.job.job_items end local headers = {} @@ -374,7 +370,7 @@ end local ScrJobDetails = df.global.game.main_interface.job_details local ScrWorkorderConditions = df.global.game.main_interface.info.work_orders.conditions -local function show_job_details() +local function get_current_job() local job local context local scr = ScrJobDetails @@ -403,6 +399,12 @@ local function show_job_details() end end + return job, context +end + +local function show_job_details() + local job, context = get_current_job() + if (job == nil) then qerror("This script needs to be run from a job details or order conditions screen") end @@ -410,6 +412,12 @@ local function show_job_details() JobDetailsScreen{ job = job, context = context }:show() end +local function is_change_possible() + -- we say it is if there is at least one item in the job + local job = get_current_job() + return job.items and #job.items ~= 0 +end + -- -------------------- -- DetailsHotkeyOverlay -- @@ -439,6 +447,7 @@ function DetailsHotkeyOverlay:init() label=LABEL_TEXT, key='CUSTOM_CTRL_D', on_activate=show_job_details, + enabled=is_change_possible, }, } end From 17ca139ee61ed357d65388a86ac409f8d253355a Mon Sep 17 00:00:00 2001 From: Timur Kelman Date: Tue, 7 Nov 2023 18:53:16 +0100 Subject: [PATCH 27/31] add an ability to reset changes --- gui/job-details.lua | 51 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/gui/job-details.lua b/gui/job-details.lua index b888d6fa15..db79108c66 100644 --- a/gui/job-details.lua +++ b/gui/job-details.lua @@ -93,11 +93,20 @@ function JobDetails:init(args) row_height = 4, scroll_keys = widgets.SECONDSCROLL, }, + widgets.HotkeyLabel{ + frame = { l = 0, b = 0 }, + key = 'CUSTOM_CTRL_Z', + label = "Reset changes", + auto_width=true, + -- enabled = self:callback('canResetChanges'), + on_activate = self:callback('onResetChanges'), + }, } self.list = self.subviews.list self:initListChoices() + self:storeInitialProperties() local h = 2 -- window border + self.list.frame.t -- everything above the list @@ -107,6 +116,33 @@ function JobDetails:init(args) self.frame.h = h end +function JobDetails:storeInitialProperties() + local stored = {} + for i,iobj in ipairs(self.job.items) do + local copy = {} + + copy.item_type = iobj.item_type + copy.item_subtype = iobj.item_subtype + + copy.mat_type = iobj.mat_type + copy.mat_index = iobj.mat_index + + for i = 1, 5 do + if not df['job_item_flags'..i] then break end + local ffield = 'flags'..i + + copy[ffield] = {} + for k,v in pairs(iobj[ffield]) do + copy[ffield][k] = v + end + end + + stored[i] = copy + end + + self.stored = stored +end + local function describe_item_type(iobj) local itemline = 'any item' if iobj.item_type >= 0 then @@ -367,6 +403,21 @@ function JobDetails:onChangeTrait() }:show() end +function JobDetails:onResetChanges() + for i, stored_obj in pairs(self.stored) do + local iobj = self.job.items[i] + for k,v in pairs(stored_obj) do + if type(v) ~= 'table' then + iobj[k] = v + else + for k1,v1 in pairs(v) do + iobj[k][k1] = v1 + end + end + end + end +end + local ScrJobDetails = df.global.game.main_interface.job_details local ScrWorkorderConditions = df.global.game.main_interface.info.work_orders.conditions From e8615655624c56d45ac409ab03d2c465517b4227 Mon Sep 17 00:00:00 2001 From: Timur Kelman Date: Tue, 7 Nov 2023 19:01:25 +0100 Subject: [PATCH 28/31] disable "change type" if Armok mode is off --- gui/job-details.lua | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/gui/job-details.lua b/gui/job-details.lua index db79108c66..bfb26494a8 100644 --- a/gui/job-details.lua +++ b/gui/job-details.lua @@ -301,6 +301,11 @@ function JobDetails:initListChoices() end function JobDetails:canChangeIType() + if dfhack.getHideArmokTools() then + -- as this could be considered an exploit + return false + end + local idx, obj = self.list:getSelected() return obj ~= nil end From 5ac5b9cd33eab82569d026f9d721a13323221a44 Mon Sep 17 00:00:00 2001 From: Timur Kelman Date: Tue, 7 Nov 2023 19:14:12 +0100 Subject: [PATCH 29/31] use special setItemType and setMaterial methods when resetting changes --- gui/job-details.lua | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/gui/job-details.lua b/gui/job-details.lua index bfb26494a8..29c2a5fbff 100644 --- a/gui/job-details.lua +++ b/gui/job-details.lua @@ -409,15 +409,24 @@ function JobDetails:onChangeTrait() end function JobDetails:onResetChanges() - for i, stored_obj in pairs(self.stored) do - local iobj = self.job.items[i] - for k,v in pairs(stored_obj) do - if type(v) ~= 'table' then - iobj[k] = v - else - for k1,v1 in pairs(v) do - iobj[k][k1] = v1 - end + for _, obj in pairs(self.list.choices) do + local stored_obj = self.stored[obj.index] + + local item_type = stored_obj.item_type + local item_subtype = stored_obj.item_subtype + self:setItemType(obj, item_type, item_subtype) + + local mat_type = stored_obj.mat_type + local mat_index = stored_obj.mat_index + self:setMaterial(obj, mat_type, mat_index) + + for i = 1, 5 do + local k = 'flags'..i + local flags = stored_obj[k] + if not flags then break end + + for k1,v1 in pairs(flags) do + obj.iobj[k][k1] = v1 end end end From 11393bcfdcd0e2ed3cfe50349a94dbffa3464bd1 Mon Sep 17 00:00:00 2001 From: Timur Kelman Date: Tue, 7 Nov 2023 19:42:30 +0100 Subject: [PATCH 30/31] fix a bug introduced in recent commit --- gui/job-details.lua | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/gui/job-details.lua b/gui/job-details.lua index 29c2a5fbff..da0d388bde 100644 --- a/gui/job-details.lua +++ b/gui/job-details.lua @@ -257,14 +257,16 @@ local function GetHeader(iobj, items, i, is_active_job) end function JobDetails:initListChoices() - local job_items = self.job.items + local job_items local items = {} local is_active_job = false if self:isManagerOrder() then - if not job_items then + if not self.job.items then self.list:setChoices({}) return end + + job_items = self.job.items else is_active_job = true @@ -274,6 +276,8 @@ function JobDetails:initListChoices() items[idx] = (items[idx] or 0) + 1 end end + + job_items = self.job.job_items end local headers = {} From 492417be5bb4b4aa61ee055a695e79a5b8b47fcc Mon Sep 17 00:00:00 2001 From: Timur Kelman Date: Fri, 10 Nov 2023 20:07:40 +0100 Subject: [PATCH 31/31] fix another bug --- gui/job-details.lua | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/gui/job-details.lua b/gui/job-details.lua index da0d388bde..12dc79c965 100644 --- a/gui/job-details.lua +++ b/gui/job-details.lua @@ -38,9 +38,13 @@ JobDetails.ATTRS { context = DEFAULT_NIL, } +local function isManagerOrder(context) + return context == df.job_details_context_type.MANAGER_WORK_ORDER + or context == df.job_details_context_type.BUILDING_WORK_ORDER +end + function JobDetails:isManagerOrder() - return self.context == df.job_details_context_type.MANAGER_WORK_ORDER - or self.context == df.job_details_context_type.BUILDING_WORK_ORDER + return isManagerOrder(self.context) end function JobDetails:init(args) @@ -118,7 +122,8 @@ end function JobDetails:storeInitialProperties() local stored = {} - for i,iobj in ipairs(self.job.items) do + for _, choice in ipairs(self.list.choices) do + local iobj = choice.iobj local copy = {} copy.item_type = iobj.item_type @@ -137,7 +142,7 @@ function JobDetails:storeInitialProperties() end end - stored[i] = copy + stored[choice.index] = copy end self.stored = stored @@ -413,16 +418,16 @@ function JobDetails:onChangeTrait() end function JobDetails:onResetChanges() - for _, obj in pairs(self.list.choices) do - local stored_obj = self.stored[obj.index] + for _, choice in pairs(self.list.choices) do + local stored_obj = self.stored[choice.index] local item_type = stored_obj.item_type local item_subtype = stored_obj.item_subtype - self:setItemType(obj, item_type, item_subtype) + self:setItemType(choice, item_type, item_subtype) local mat_type = stored_obj.mat_type local mat_index = stored_obj.mat_index - self:setMaterial(obj, mat_type, mat_index) + self:setMaterial(choice, mat_type, mat_index) for i = 1, 5 do local k = 'flags'..i @@ -430,7 +435,7 @@ function JobDetails:onResetChanges() if not flags then break end for k1,v1 in pairs(flags) do - obj.iobj[k][k1] = v1 + choice.iobj[k][k1] = v1 end end end @@ -483,8 +488,12 @@ end local function is_change_possible() -- we say it is if there is at least one item in the job - local job = get_current_job() - return job.items and #job.items ~= 0 + local job, context = get_current_job() + if isManagerOrder(context) then + return job.items and #job.items ~= 0 + else + return job.job_items and #job.job_items ~= 0 + end end -- --------------------