--[[ @Title: Check Sort Dates @Type: Standard @Author: Mark Draper @Version: 1.1 @LastUpdated: 23 Jun 2025 @Licence: This plugin is copyright (c) 2025 Mark Draper, and is licensed under the MIT License which is hereby incorporated by reference (see https://pluginstore.family-historian.co.uk/fh-plugin-licence) @Description: Lists and optionally deletes sort dates that are either invalid or unnecessary ]] fhInitialise(7, 0, 0, 'save_required') require('iuplua') require 'luacom' fh = require('fhUtils') fh.setIupDefaults() iup.SetGlobal('UTF8MODE_FILE','YES') function main() local selection -- create menu local Title = iup.label{title = 'Select option to check project sort dates', padding = '10x10'} local btnSortDates = iup.button{title = 'Check sort dates', size = '80x15'} local btnHelp = iup.button{title = 'Help'} local btnClose = iup.button{title = 'Close'} local Buttons = iup.vbox{btnSortDates, btnHelp, btnClose; normalizesize = 'BOTH', gap = 10} local vbox = iup.vbox{Title, Buttons; alignment = 'ACENTER', gap = 10; margin = '20x20'} local dialog = iup.dialog{vbox; resize = 'No', minbox = 'No', maxbox = 'No', border = 'NO', title = 'Check Sort Dates (1.1)'} iup.SetAttribute(dialog, 'NATIVEPARENT', fhGetContextInfo('CI_PARENT_HWND')) function btnSortDates:action() selection = 1 return iup.CLOSE end function btnHelp:action() selection = 2 return iup.CLOSE end function btnClose:action() selection = 0 return iup.CLOSE end repeat selection = 0 dialog:show() iup.MainLoop() dialog:hide() if selection == 1 then selection = SortDates() elseif selection == 2 then local Cmd = 'https://pluginstore.family-historian.co.uk/page/help/check-sort-dates' fhShellExecute(Cmd) fhSleep(1000) -- slight pause to suspend immediate redraw end until not selection or selection == 0 end -- ************************************************************************** -- function SortDates() -- deletes all invalid sort dates (those that do not resolve as dates) and superfluous sort dates -- (equal to actual date or start of range) local p = fhNewItemPtr() local pR = fhNewItemPtr() local tblX = {} local CountI, CountS = 0, 0 for _, Type in ipairs({'INDI', 'FAM'}) do pR:MoveToFirstRecord(Type) while pR:IsNotNull() do p:MoveToFirstChildItem(pR) while p:IsNotNull() do local pD = fhGetItemPtr(p, '~.DATE') local pSD = fhGetItemPtr(p, '~._SDATE') if pSD:IsNotNull() then local dtSDate = fhGetValueAsDate(pSD) if dtSDate:IsNull() then -- invalid date table.insert(tblX, pSD:Clone()) CountI = CountI + 1 else local dtDate = fhGetValueAsDate(pD) local dpDate = dtDate:GetDatePt1() local dpSDate = dtSDate:GetDatePt1() if dpDate:Compare(dpSDate) == 0 then table.insert(tblX, pSD:Clone()) CountS = CountS + 1 end end end p:MoveNext() end pR:MoveNext() end end if #tblX == 0 then MessageBox('No invalid or superfluous sort dates identified.', 'OK', 'INFORMATION') return true end local msg = CountI .. ' invalid and ' .. CountS .. ' superfluous sort dates were identified.\n' if CountI + CountS == 1 then msg = msg:gsub('dates were', 'date was') end if #tblX == 1 then msg = msg .. 'What do you want to do with it?' else msg = msg .. 'What do you want to do with them?' end local Action = AlarmForm('Process Sort Dates', msg, 'List', 'Delete', 'Cancel') if Action == 3 then return true elseif Action == 2 then for _, p in ipairs(tblX) do fhDeleteItem(p) end fhUpdateDisplay() return true elseif Action == 1 then local tblXiE = {} local tblXiED = {} local tblXiR = {} local tblXSD = {} for _, p in ipairs(tblX) do local pE = fhNewItemPtr() pE:MoveToParentItem(p) local pED = fhGetItemPtr(pE, '~.DATE') local pR = fhNewItemPtr() pR:MoveToParentItem(pE) table.insert(tblXiE, pE:Clone()) table.insert(tblXiED, pED:Clone()) table.insert(tblXiR, pR:Clone()) table.insert(tblXSD, p:Clone()) end fhOutputResultSetTitles('Sort Dates') fhOutputResultSetColumn('Record', 'item', tblXiR, #tblXiR, 200, 'align_left', 0, true, 'default') fhOutputResultSetColumn('Fact', 'item', tblXiE, #tblXiE, 175, 'align_left', 0, true, 'default') fhOutputResultSetColumn('Date', 'item', tblXiED, #tblXiED, 160, 'align_left', 0, true, 'date') fhOutputResultSetColumn('Sort Date', 'item', tblXSD, #tblXSD, 80, 'align_left', 0, true, 'date') end end -- ************************************************************************** -- function AlarmForm(formtitle, heading, txt1, txt2, txt3) -- create form modelled on iup.alarm (original iup.alarm does not support multi-monitor setup) local action = 0 local btn1 = iup.button{title = txt1, padding = '10x3', action = function(self) action = 1 return iup.CLOSE end} local btn2 = iup.button{title = txt2, padding = '10x3', action = function(self) action = 2 return iup.CLOSE end} local btn3 = iup.button{title = txt3, padding = '10x3', action = function(self) action = 3 return iup.CLOSE end} local buttons = iup.hbox{iup.fill{}, btn1, btn2, btn3, iup.fill{}; margin = '0x10', normalizesize = 'Both', padding = 10, gap = 20} local label = iup.label{title = heading} local vbox = iup.vbox{label, buttons; gap = 10, margin = '20x20'} local dialog = iup.dialog{vbox; title = formtitle, resize = 'No', minbox = 'No'} if fhGetAppVersion() > 6 then iup.SetAttribute(dialog, 'NATIVEPARENT', fhGetContextInfo('CI_PARENT_HWND')) end dialog:popup() return action end -- ******************************************************************************************************-- function MessageBox(Message, Buttons, Icon, Title, Default) -- replaces built-in function with custom version containing more options local msgdlg = iup.messagedlg{value = Message, buttons = Buttons, dialogtype = Icon, title = Title or 'Check Sort Dates (1.1)', buttondefault = Default} msgdlg:popup() return tonumber(msgdlg.ButtonResponse) end -- ******************************************************************************************************-- main()