Rich Text Tabulation.fh_lua

--[[
@Title:       Rich Text Tabulation
@Type:        Standard
@Author:      Mark Draper
@Version:     1.1
@LastUpdated: 13 Aug 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: Tabulates project fields containing selected Rich Text elements.
]]

--[[
Version 0.1 - 19 Dec 2022
	Original FHUG prototype
Version 1.0 - 3 Jan 2023
	First Store version
Version 1.1 - 13 Aug 2025
	App Data folder selection corrected
]]

fhInitialise(7)

require('iuplua')
fh = require('fhUtils')
fhfu = require('fhFileUtils')

fh.setIupDefaults()

function main()

	local p = fhNewItemPtr()
	local pR = fhNewItemPtr()
	local tblRecord = {}
	local tblText = {}

	-- present menu and capture selected options

	local tblSelection = MenuOptions()
	if tblSelection.End then return end

	-- search all records in the project for Rich Text fields

	local count = 0
	for i = 1, fhGetRecordTypeCount() do
		p:MoveToFirstRecord(fhGetRecordTypeTag(i))
		while p:IsNotNull() do
			if fhGetValueType(p) == 'richtext' then
				local rt = fhGetValueAsRichText(p)
				if rt:IsRich() then
					count = count + 1
					if tblSelection.All or SelectField(rt, tblSelection) then
						pR:MoveToRecordItem(p)
						table.insert(tblRecord, pR:Clone())
						table.insert(tblText, p:Clone())
					end
				end
			end
			p:MoveNextSpecial()
		end
	end

	if count == 0 then
		fhMessageBox('This project does not use Rich Text', 'MB_OK', 'MB_ICONINFORMATION')
		return
	end

	local message = ''

	if not tblSelection.All then
		message = #tblText .. ' fields containing selected features\n\n'
		if #tblText == 1 then message = message:gsub('fields', 'field') end
	end

	if count == 1 then
		message = message .. count .. ' Rich Text field in total'
	else
		message = message .. count .. ' Rich Text fields in total'
	end

	fhMessageBox(message, 'MB_OK', 'MB_ICONINFORMATION')

	if #tblText > 0 then
		fhOutputResultSetTitles('Rich Text')
		fhOutputResultSetColumn('Record', 'item', tblRecord, #tblRecord)
		fhOutputResultSetColumn('Rich Text', 'item', tblText, #tblText)
	end
end

-- ************************************************************************** --

function MenuOptions()

	-- create menu

	local Options = {}			-- toggles in table for easier management
	local tblSelection = {}

	tblSelection.End = true		-- default if not changed

	-- define font selection frame using long font name over 2 lines for max size

	Options.None = iup.toggle{title = 'None', expand = 'YES'}
	Options.Specified = iup.toggle{title = 'Matura MT Script\nCapitals', expand = 'YES'}
	Options.Any = iup.toggle{title = 'Any', expand = 'YES'}
	local btnSelect = iup.button{title = 'Select', padding = '10x3', tip = 'Select font to locate'}

	local vboxFont = iup.vbox{Options.None, Options.Specified, Options.Any; gap = 5, margin = '0x0'}
	local radio = iup.radio{vboxFont}
	local vboxFS = iup.vbox{radio, btnSelect; gap = 10, margin = '10x10'}
	local fraFS = iup.frame{vboxFS; title = 'Font Selection'}

	-- define other features

	Options.Tables = iup.toggle{title = 'Tables', expand = 'YES'}
	Options.Citations = iup.toggle{title = 'Citations', expand = 'YES'}
	Options.RecLinks = iup.toggle{title = 'Record Links', expand = 'YES'}
	Options.ExtLinks = iup.toggle{title = 'External Links', expand = 'YES'}

	local vboxOF = iup.vbox{Options.Tables, Options.Citations, Options.RecLinks, Options.ExtLinks;
			gap = 10, margin = '10x10'}
	local fraOF = iup.frame{vboxOF; title = 'Other Features'}

	-- define custom tag

	Options.Custom = iup.toggle{title = '', expand = 'YES'}
	local btnDefine = iup.button{title = 'Define', padding = '10x3', tip = 'Define custom Rich Text tag'}

	local vboxCF = iup.vbox{Options.Custom, btnDefine; gap = 10, margin = '10x10'}
	local fraCF = iup.frame{vboxCF; title = 'Custom Tag'}

	-- define main buttons

	local btnOK = iup.button{title = 'OK', tip = 'Locate and tabulate all record fields containing at\n' ..
			'least one of the selected Rich Text features'}
	local btnAll = iup.button{title = 'All Rich Text', padding = '5x3',
			tip = 'Locate and tabulate all record fields defined as Rich Text'}
	local btnHelp = iup.button{title = 'Help', tip = 'Display Plugin Store Help page'}
	local btnClose = iup.button{title = 'Close', tip = 'Close plugin'}

	-- define enhanced tool tips

	for _, ctrl in ipairs({btnSelect, btnDefine, btnOK, btnAll, btnHelp, btnClose}) do
		ctrl.TipBalloon = 'YES'
		ctrl.TipBalloonTitleIcon = 1
	end

	btnSelect.TipBalloonTitle = 'Select Font'
	btnDefine.TipBalloonTitle = 'Define Custom Tag'
	btnOK.TipBalloonTitle = 'Tabulate Rich Text'
	btnAll.TipBalloonTitle = 'Tabulate All Rich Text'
	btnHelp.TipBalloonTitle = 'Get Plugin Help'
	btnClose.TipBalloonTitle = 'Close Plugin'

	local Buttons = iup.hbox{btnOK, btnAll, btnHelp, btnClose; normalizesize = 'BOTH', gap = 25}

	-- build form

	local Title = iup.label{title = 'List fields containing at least one of the selected Rich Text features',
			padding = '10x10'}

	local hboxForm = iup.hbox{fraFS, fraOF, fraCF, normalizesize = 'BOTH'}
	local vboxForm = iup.vbox{Title, hboxForm, Buttons; alignment = 'ACENTER', gap = 10; margin = '10x10'}
	local dialog = iup.dialog{vboxForm; resize = 'NO', minbox = 'NO', maxbox = 'NO', border = 'NO',
			title = 'Rich Text Tabulation'}

	-- define callbacks

	function btnSelect:action()
		local fullfont = SelectFont() or ''
		Options.Specified.Title = fullfont:match('^[^,]+') or Options.Specified.Title
		Options.Specified.Value = 'YES'
		Options.Specified.Size = size
	end

	function btnOK:action()
		for k, v in pairs(Options) do
			if v.Value == 'ON' then
				tblSelection[k] = true
			end
		end
		if Options.Specified.Value == 'ON' then
			tblSelection.Specified = Options.Specified.Title
		end
		if Options.Custom.Value == 'ON' then
			tblSelection.Custom = Options.Custom.Title
		end
		tblSelection.End = nil
		return iup.CLOSE
	end

	function btnAll:action()
		tblSelection.All = true
		tblSelection.End = nil
		return iup.CLOSE
	end

	function btnHelp:action()
		local Cmd = 'https://pluginstore.family-historian.co.uk/page/help/rich-text-tabulation'
		fhShellExecute(Cmd)
	end

	function btnDefine:action()
		local tag = iup.GetText('Enter custom Rich Text tag', Options.Custom.Title or '')
		if tag and tag:match('^<.+>$') and not tag:match('\n') then
			Options.Custom.Title = tag
			Options.Custom.Value = 'ON'
		end
	end

	function btnClose:action()
		return iup.CLOSE
	end

	-- show form

	dialog:map()
	local size = Options.Specified.Size
	GetOptions(Options)								-- populate form with saved values
	Options.Specified.Size = size
	Options.Custom.Size = size

	iup.SetAttribute(dialog, 'NATIVEPARENT', fhGetContextInfo('CI_PARENT_HWND'))

	dialog:show()
	iup.MainLoop()
	SaveOptions(Options)							-- save form configuration
	dialog:destroy()

	return tblSelection
end

-- ************************************************************************** --

function GetOptions(Options)

	-- get options (system level)

	local File = fhGetContextInfo('CI_APP_DATA_FOLDER') .. '\\Plugin Data\\Rich Text Tabulation.ini.'

	Options.None.Value = fhGetIniFileValue(File, 'Font', 'None', 'text', 'ON')
	Options.Specified.Value = fhGetIniFileValue(File, 'Font', 'Specified', 'text', 'OFF')
	Options.Specified.Title = fhGetIniFileValue(File, 'Font', 'Font', 'text',
			iup.GetGlobal('DEFAULTFONTFACE'):match('^[^",]+'))
	Options.Any.Value = fhGetIniFileValue(File, 'Font', 'Any', 'text', 'OFF')
	Options.Tables.Value = fhGetIniFileValue(File, 'Other', 'Tables', 'text', 'OFF')
	Options.Citations.Value = fhGetIniFileValue(File, 'Other', 'Citations', 'text', 'OFF')
	Options.RecLinks.Value = fhGetIniFileValue(File, 'Other', 'RecLinks', 'text', 'OFF')
	Options.ExtLinks.Value = fhGetIniFileValue(File, 'Other', 'ExtLinks', 'text', 'OFF')
	Options.Custom.Title = fhGetIniFileValue(File, 'Custom', 'Tag', 'text', '')
	Options.Custom.Value = fhGetIniFileValue(File, 'Custom', 'Value', 'text', 'OFF')
end

-- ************************************************************************** --

function SaveOptions(Options)

	-- save options to disk (system level)

	-- create options folder if necessary

	local Folder = fhGetContextInfo('CI_APP_DATA_FOLDER') .. '\\Plugin Data'

	if not fhfu.folderExists(Folder) then
		if not fhfu.createFolder(Folder) then return end
	end

	-- create and write to options file

	local File = Folder .. '\\' .. 'Rich Text Tabulation.ini.'

	fhSetIniFileValue(File, 'Font', 'None', 'text', Options.None.Value)
	fhSetIniFileValue(File, 'Font', 'Specified', 'text', Options.Specified.Value)
	fhSetIniFileValue(File, 'Font', 'Font', 'text', Options.Specified.Title)
	fhSetIniFileValue(File, 'Font', 'Any', 'text', Options.Any.Value)
	fhSetIniFileValue(File, 'Other', 'Tables', 'text', Options.Tables.Value)
	fhSetIniFileValue(File, 'Other', 'Citations', 'text', Options.Citations.Value)
	fhSetIniFileValue(File, 'Other', 'RecLinks', 'text', Options.RecLinks.Value)
	fhSetIniFileValue(File, 'Other', 'ExtLinks', 'text', Options.ExtLinks.Value)
	fhSetIniFileValue(File, 'Custom', 'Tag', 'text', Options.Custom.Title)
	fhSetIniFileValue(File, 'Custom', 'Value', 'text', Options.Custom.Value)
end

-- ************************************************************************** --

function SelectFont()

	local fontdlg = iup.fontdlg{title='Select Font (Font style, Size and Effects are discarded)'}
	fontdlg:popup()
	local font = fontdlg.Value

	return font
end

-- ************************************************************************** --

function SelectField(rt, tblSelection)

	-- returns true if field contains specified Rich Text

	local ok = false
	local tag = rt:GetText()

	if tblSelection.Specified then
		local font = tblSelection.Specified
		if tag:match('') then ok = true end
	end

	if tblSelection.Any and tag:match('') then ok = true end
	if tblSelection.Citations and tag:match('') then ok = true end
	if tblSelection.Custom and tag:match(tblSelection.Custom) then ok = true end

	return ok
end

-- ************************************************************************** --

main()

Source:Rich-Text-Tabulation-2.fh_lua