Skip to content

Commit 9a2a261

Browse files
committed
Merge remote-tracking branch 'myk002/myk_blueprint_name'
2 parents b53094a + 217de3c commit 9a2a261

2 files changed

Lines changed: 194 additions & 3 deletions

File tree

gui/blueprint.lua

Lines changed: 90 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ local blueprint = require('plugins.blueprint')
2323
local dialogs = require('gui.dialogs')
2424
local gui = require('gui')
2525
local guidm = require('gui.dwarfmode')
26+
local utils = require('utils')
2627
local widgets = require('gui.widgets')
2728

2829
ResizingPanel = defclass(ResizingPanel, widgets.Panel)
@@ -84,6 +85,68 @@ function ActionPanel:get_area_text()
8485
return ('%dx%dx%d (%d tile%s)'):format(width, height, depth, tiles, plural)
8586
end
8687

88+
NamePanel = defclass(NamePanel, ResizingPanel)
89+
NamePanel.ATTRS{
90+
name='blueprint',
91+
}
92+
function NamePanel:init()
93+
self:addviews{
94+
widgets.EditField{
95+
view_id='name',
96+
frame={t=0,h=1},
97+
key='CUSTOM_N',
98+
active=false,
99+
text=self.name,
100+
on_change=self:callback('detect_name_collision'),
101+
},
102+
widgets.Label{
103+
view_id='name_help',
104+
frame={t=1,l=2},
105+
text={{text=self:callback('get_name_help', 1),
106+
pen=self:callback('get_help_pen')}, '\n',
107+
{text=self:callback('get_name_help', 2),
108+
pen=self:callback('get_help_pen')}}
109+
},
110+
}
111+
112+
self:detect_name_collision()
113+
end
114+
function NamePanel:detect_name_collision()
115+
-- don't let base names start with a slash - it would get ignored by
116+
-- the blueprint plugin later anyway
117+
local name = utils.normalizePath(self.subviews.name.text):gsub('^/','')
118+
self.subviews.name.text = name
119+
120+
if name == '' then
121+
self.has_name_collision = false
122+
return
123+
end
124+
125+
local suffix_pos = #name + 1
126+
127+
local paths = dfhack.filesystem.listdir_recursive('blueprints', nil, false)
128+
for _,v in ipairs(paths) do
129+
if (v.isdir and v.path..'/' == name) or
130+
(v.path:startswith(name) and
131+
v.path:sub(suffix_pos,suffix_pos):find('[.-]')) then
132+
self.has_name_collision = true
133+
return
134+
end
135+
end
136+
self.has_name_collision = false
137+
end
138+
function NamePanel:get_name_help(line_number)
139+
if self.has_name_collision then
140+
return ({'Warning: may overwrite',
141+
'existing files.'})[line_number]
142+
end
143+
return ({'Set base name for the',
144+
'generated blueprint files.'})[line_number]
145+
end
146+
function NamePanel:get_help_pen()
147+
return self.has_name_collision and COLOR_RED or COLOR_GREY
148+
end
149+
87150
BlueprintUI = defclass(BlueprintUI, guidm.MenuOverlay)
88151
BlueprintUI.ATTRS {
89152
presets={},
@@ -100,6 +163,7 @@ function BlueprintUI:init()
100163
widgets.Label{text='Blueprint'},
101164
widgets.Label{text=summary, text_pen=COLOR_GREY},
102165
ActionPanel{get_mark_fn=function() return self.mark end},
166+
NamePanel{name=self.presets.name},
103167
widgets.Label{view_id='cancel_label',
104168
text={{text=function() return self:get_cancel_label() end,
105169
key='LEAVESCREEN', key_sep=': ',
@@ -205,6 +269,31 @@ function BlueprintUI:onRenderBody()
205269
end
206270

207271
function BlueprintUI:onInput(keys)
272+
-- the 'name' edit field must have its 'active' state managed at this level.
273+
-- we also have to implement 'cancel edit' logic here
274+
local name_view = self.subviews.name
275+
if not name_view.active and keys[name_view.key] then
276+
self.saved_name = name_view.text
277+
if name_view.text == 'blueprint' then
278+
name_view.text = ''
279+
name_view:on_change()
280+
end
281+
name_view.active = true
282+
return true
283+
end
284+
if name_view.active then
285+
if keys.SELECT or keys.LEAVESCREEN then
286+
name_view.active = false
287+
if keys.LEAVESCREEN or name_view.text == '' then
288+
name_view.text = self.saved_name
289+
name_view:on_change()
290+
end
291+
return true
292+
end
293+
name_view:onInput(keys)
294+
return true
295+
end
296+
208297
if self:inputToSubviews(keys) then return true end
209298

210299
local pos = nil
@@ -241,7 +330,7 @@ function BlueprintUI:commit(pos)
241330
depth = -depth
242331
end
243332

244-
local name = 'blueprint'
333+
local name = self.subviews.name.text
245334
local params = {tostring(width), tostring(height), tostring(depth), name}
246335

247336
-- set cursor to top left corner of the *uppermost* z-level

test/gui/blueprint.lua

Lines changed: 104 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
1+
local function test_wrapper(test_fn)
2+
mock.patch(dfhack.filesystem, 'listdir_recursive', mock.func({}), test_fn)
3+
end
4+
15
config = {
26
mode = 'fortress',
7+
wrapper = test_wrapper,
38
}
49

510
local b = reqscript('gui/blueprint')
@@ -24,7 +29,9 @@ local function send_keys(...)
2429
end
2530

2631
local function load_ui()
27-
local view = b.BlueprintUI{}
32+
local options = {}
33+
blueprint.parse_gui_commandline(options, {})
34+
local view = b.BlueprintUI{presets=options}
2835
view:show()
2936
return view
3037
end
@@ -201,7 +208,8 @@ function test.preset_cursor()
201208
local view = b.active_screen
202209
expect.table_eq({x=11, y=12, z=13}, guidm.getCursorPos())
203210
expect.true_(not not view.mark)
204-
send_keys('LEAVESCREEN', 'LEAVESCREEN') -- cancel selection and ui
211+
-- cancel selection, ui, and look mode
212+
send_keys('LEAVESCREEN', 'LEAVESCREEN', 'LEAVESCREEN')
205213
end
206214

207215
--auto enter and leave cursor-supporting mode
@@ -342,5 +350,99 @@ function test.render_status_line()
342350
end
343351

344352
-- edit widget for setting the blueprint name
353+
function test.preset_basename()
354+
dfhack.run_script('gui/blueprint', 'imaname')
355+
local view = b.active_screen
356+
expect.eq('imaname', view.subviews.name.text)
357+
send_keys('LEAVESCREEN') -- leave UI
358+
end
359+
360+
function test.edit_basename()
361+
local view = load_ui()
362+
local name_widget = view.subviews.name
363+
expect.eq('blueprint', name_widget.text)
364+
send_keys('CUSTOM_N')
365+
expect.eq('', name_widget.text)
366+
view:onInput({_STRING=string.byte('h')})
367+
view:onInput({_STRING=string.byte('i')})
368+
send_keys('SELECT')
369+
expect.eq('hi', name_widget.text)
370+
send_keys('LEAVESCREEN') -- leave UI
371+
end
372+
373+
function test.cancel_name_edit()
374+
local view = load_ui()
375+
local name_widget = view.subviews.name
376+
expect.eq('blueprint', name_widget.text)
377+
send_keys('CUSTOM_N')
378+
expect.eq('', name_widget.text)
379+
view:onInput({_STRING=string.byte('h')})
380+
view:onInput({_STRING=string.byte('i')})
381+
send_keys('LEAVESCREEN') -- cancel edit
382+
expect.eq('blueprint', name_widget.text)
383+
send_keys('LEAVESCREEN') -- leave UI
384+
end
385+
386+
function test.name_no_collision()
387+
mock.patch(dfhack.filesystem, 'listdir_recursive',
388+
mock.func({{path='blue-dig.csv'}}),
389+
function()
390+
local view = load_ui()
391+
view:updateLayout()
392+
local name_help_label = view.subviews.name_help
393+
local name_help_text_pos = {x=name_help_label.frame_body.x1,
394+
y=name_help_label.frame_body.y1}
395+
view:onRender()
396+
expect.eq('Set', get_screen_word(name_help_text_pos))
397+
send_keys('LEAVESCREEN') -- cancel ui
398+
end)
399+
400+
mock.patch(dfhack.filesystem, 'listdir_recursive',
401+
mock.func({{path='blueprint'}}),
402+
function()
403+
local view = load_ui()
404+
view.subviews.name.text = 'blueprint/'
405+
view.subviews.name.on_change()
406+
view:updateLayout()
407+
local name_help_label = view.subviews.name_help
408+
local name_help_text_pos = {x=name_help_label.frame_body.x1,
409+
y=name_help_label.frame_body.y1}
410+
view:onRender()
411+
expect.eq('Set', get_screen_word(name_help_text_pos),
412+
'dirname does not conflict with similar filename')
413+
send_keys('LEAVESCREEN') -- cancel ui
414+
end)
415+
end
416+
417+
function test.name_collision()
418+
mock.patch(dfhack.filesystem, 'listdir_recursive',
419+
mock.func({{path='blueprint-dig.csv'}}),
420+
function()
421+
local view = load_ui()
422+
view:updateLayout()
423+
local name_help_label = view.subviews.name_help
424+
local name_help_text_pos = {x=name_help_label.frame_body.x1,
425+
y=name_help_label.frame_body.y1}
426+
view:onRender()
427+
expect.eq('Warning:', get_screen_word(name_help_text_pos))
428+
send_keys('LEAVESCREEN') -- cancel ui
429+
end)
430+
431+
mock.patch(dfhack.filesystem, 'listdir_recursive',
432+
mock.func({{path='blueprint', isdir=true}}),
433+
function()
434+
local view = load_ui()
435+
view.subviews.name.text = 'blueprint/'
436+
view.subviews.name.on_change()
437+
view:updateLayout()
438+
local name_help_label = view.subviews.name_help
439+
local name_help_text_pos = {x=name_help_label.frame_body.x1,
440+
y=name_help_label.frame_body.y1}
441+
view:onRender()
442+
expect.eq('Warning:', get_screen_word(name_help_text_pos),
443+
'dirname match generates warning')
444+
send_keys('LEAVESCREEN') -- cancel ui
445+
end)
446+
end
345447

346448
-- widgets to configure which blueprint phases to output

0 commit comments

Comments
 (0)