Simple Marriage Certificate.fh_lua

--[[
@Title: Simple Marriage Certificate 
@Type: Standard
@Author: [Norman Martin]
@Version: 1.1 
@Keywords: 
@LastUpdated: 14 April 2024
@Licence: This plugin is copyright (c) Norman Martin 2024 and contributors, and is licensed under the MIT License
which is hereby incorporated by reference (see https://pluginstore.family-historian.co.uk/fh-plugin-licence)
@Description: Designed to work with 'Civil Registration Certificate' Source Template from the Essentials collection,
it will record the details of a marriage certificate, for the current individual, based on the format of the 
England and Wales Marriage Certificate/Parish Register since 1837.

Where the information is provided, it will also the residence and occupation of the husband, 
and the occupations of their respective fathers at the time of the marriage.

For further support please post in the appropriate Family Historian User Group forums (FHUG) topic or a reply at the end of this download page, 
where the plugin author will answer your questions which can be supplemented with screenshots if necessary.

@changes:
1.2 Added Vertical Scroll bar for certificate deatils to cater for different screen resolutions.
1.1 Amended Help and Support Options
]]

local help=[[
This plugin records a Marriage Certificate based on the format of the England and Wales Marriage Certificate/Parish Register since 1837.
It also creates of marriage, occupation and residence facts. To ensure consistency, it uses the Civil Registration Certificate Template
for the source.

Prerequisites

Before it can be used, the "Civil Registration Certificate" template from the Essentials collection
must have already been added to the project. This can be done by selecting Tools>Source Template Definitions 
and then highlight the template(s) and add to project.
This plugin does NOT create any individuals. Before it is called, It requires that a person is first selected
and that that person is married. 
Any fathers should be created before the plugin is called, as any name changed on the form will be considered as information 
printed on the certificate (text from source).
To avoid replication of input, the following fields are saved to be displayed next time the plugin is used:
Region, Repository, Assessment, Image Directory and Media Directory.
The Region is a dropdown list of Regions declared in the source template; Repository is a dropdown list of Repositories you have set up;
and Assessment is a dropdown list of valid assessments for the source.
Image Directory is the windows directory where the marriage certificate image can be found. It can be populated by typing the directory
name in manually, or found by pressing the 'Image Directory' button to the left of the field and showing a Windows Folder Dialog screen.
Media Directory is where the image of the Marriage Certificate will be saved. It can be populated in the same method for the
Image Directory. Note you can create the appropriate media directory in the Windows Folder Dialog screen.

Form Fields

Region (Required):- this is a list of Regions declared in the source template
Repository	: - this is a list of Repositories you have already set up
Where Married : - Address where marriage took place e.g. Parish Church (if known)
Marriage Place (Required)	:   Place married - either select from drop down for existing place or type new place
which will be added as a new place
Entry Number: - Entry number of certificate (if known)
Date of Marriage (Required): - Date marriage took place
Name and Surname: -Pre selected on loading form - can be amended
Age : -Age when married (can be text e.g. Full Age)
Condition : - Dropdown list of condition (e.g. Bachelor, Spinster etc)
Rank or Profession: - Description of job (if known)			
Residence at time of marriage: - Address of husband/wife living when married (if known)
Place : - Place  husband/wife were living (if known) 
Fathers name and surname: - Pre selected on loading form - can be amended
Rank of Profession (father): - Description of job (if known)
Rites and Ceremonies : - if stated (e.g. Established Church)
Marriage Type : - Dropdown list of marriage types (e.g. After Banns, By Licence etc)
In the Presence of : - List of witnesses to marriage
Married By (Required) : - Name of person performing the marriage
Assessment : - Dropdown list of valid assessments
Image Directory : - Directory to initiate search for image of certificate 
Media Directory (Required): - Directory where image will be saved
As Source : - Dropdown list to determine name given to file when saved in the media directory
              Options are As Source - filename same as source title
                          As Filename - filename same as original filename
                         My Choice - type your own filename in Image File field
Media : - Button to invoke windows file dialog to identify certificate image to be copied
Image File (Required): - name of media file to be created
Status :	 - status of form - e.g. errors or certificate saved

When you press the save button, the form is validated and any errors reported in the status bar.
If the action is successful, then the appropriate message is shown in that status bar.

When the certificate is saved, the ini file is updated to retain the Region, Repository, Assessment, Image Directory
and Media Directory fields.

Saving a Certificate will create a source record for the marriage certificate and create a formatted 'text from source'
and a link to a newly created media record. The source record is linked to the Marriage Fact. 
The text written to the ‘text from source’ contains the values derived from the form as you have entered them. 
This text can later be amended in the Citation record in Family Historian to correct any textual errors.

If the information has been provided, then the appropriate occupation and residence facts are created and linked to the source record. 
These can be for the husband, wife and their fathers (occupation only). Age will only saved as a text if a valid age (e.g. 25) is entered.

You will need to press the Exit button, after saving, to exit the form and return back to Family Historian.
]]

-- << Comment in the next 2 lines if you need to use IUP >>
require("iuplua");
iup.SetGlobal("CUSTOMQUITMESSAGE","YES");
fh = require("fhUtils");
fhfu =require("fhFileUtils")

local plugIn="Simple Marriage Certificate" -- plugin name
local hasUsed="No"							-- variable to decide to show welcome screen
local dlgSize="440x350"

fhInitialise(7);  -- implies users will need version 7 as a minimum; change if that is not the case

local iniFile=""
local templateID=-1
local currPtr = fhNewItemPtr()
local ptrFamily=fhNewItemPtr();
local husbandPtr = fhNewItemPtr()
local wifePtr = fhNewItemPtr()
local husbandFatherPtr = fhNewItemPtr()
local wifeFatherPtr = fhNewItemPtr()
local curPerson=""
local curDates=""
local ptrTemplate=fhNewItemPtr()
local ptrRepos=fhNewItemPtr()
local region=""		
local type="Marriage"
local assessment=""
local address=""	
local source=-1
local haveCitation=false
local Places={}
local Families={}
local selFile=""

-------------------------------------------------------
-- Define Dialog Fields
-------------------------------------------------------
local color = {error = '255 200 200',red = '255 0 0',green = '0 128 0', clear='255 255 255'}
local dte_Today = iup.datepick{name='TodaysDate',size='1'}

local lst_Region  = iup.list{name='Region',expand='HORIZONTAL', dropdown="YES"}
local lst_Repos = iup.list{name='Repository',expand='HORIZONTAL', dropdown="YES"}
local txt_Address = iup.text{name='Address',expand='HORIZONTAL' }
local lst_MarriagePlace  = iup.list{name='MarriagePlace',expand='HORIZONTAL',editbox='YES', dropdown="NO",visiblelines=3}
local txt_EntryNo = iup.text{name='EntryNo',expand='HORIZONTAL' }
local dte_Marriagedate = iup.datepick{name='MarriageDate',expand='HORIZONTAL'}
local txt_Husband = iup.text{name='Husband',size='150' }
local txt_Wife = iup.text{name='Wife',size='150'}
local txt_HusbandAge = iup.text{name='HusbandAge',size='150' }
local txt_WifeAge = iup.text{name='WifeAge',size='150' }
local lst_HusbandCond = iup.list{name='HusbandCond',size='150', dropdown="YES" }
local lst_WifeCond = iup.list{name='WifeCond',size='150', dropdown="YES"}
local txt_HusbandOcc = iup.text{name='HusbandOcc',size='150' }
local txt_WifeOcc = iup.text{name='WifeOcc',size='150'}
local txt_HusbandAddress = iup.text{name='HusbandAddress',size='150'}
local txt_WifeAddress = iup.text{name='WifeAddress',size='150'}
local lst_HusbandPlace  = iup.list{name='HusbandPlace',size='150',editbox='YES', dropdown="NO",visiblelines=3}
local lst_WifePlace  = iup.list{name='WifePlace',size='150',editbox='YES', dropdown="NO",visiblelines=3}
local txt_HusbandFather = iup.text{name='HusbandFather',size='150' }
local txt_WifeFather = iup.text{name='WifeFather',size='150'}
local txt_HusbandFatherOcc = iup.text{name='HusbandFatherOcc',size='150' }
local txt_WifeFatherOcc = iup.text{name='WifeFatherOcc',size='150' }

local txt_Rites = iup.text{name='Rites',expand='HORIZONTAL' }
local lst_MarriageType  = iup.list{name='MarriageType',expand='HORIZONTAL', dropdown="YES"}
local txt_Informant  = iup.text{name='Informant',expand='HORIZONTAL', multiline='YES',visiblelines=2}
local lst_Assessment  = iup.list{name='Assessment',expand='HORIZONTAL', dropdown="YES"}
local txt_Registrar = iup.text{name='Notes',expand='HORIZONTAL'}
local lab_message = iup.label{title=' ',fgcolor=color.red, expand='HORIZONTAL'}
local lab_husband = iup.label{title='Husband ',size='150'}
local lab_wife = iup.label{title='Wife ',size='150'}

local inp_img_dir = iup.text{name='ImageDir',expand='HORIZONTAL' }
local inp_Media_dir  = iup.text{name='MediaDir',  expand='HORIZONTAL', }
local inp_name  = iup.text{Name='Filter',  expand='HORIZONTAL',readonly="YES"  }
local inp_file_list  = iup.label{name='FileList',expand="HORIZONTAL", }
local lst_name  = iup.list{name='Filename',"As Source","As Filename","My Choice",value=1;  dropdown="YES"}
local lab_Mess = iup.label{title='Please select a file to select the image to copy ',fgcolor=color.blue, expand='HORIZONTAL'}

local bt_Save = iup.button{name='save', title='Save'}
local btn_Cancel = iup.button{name='cancel', title='Cancel'}
local btn_Media = iup.button{name='source', title='Media'}
local btn_help = iup.button {name='help', title='Help'}
local btn_mediaDir=iup.button{name='MediaDir', title='Media Directory>>'} 
local btn_imageDir=iup.button{name='ImageDir', title='Image Directory>>'} 
local info_Mess = iup.label{title='---------------Marriage Certificate Details-------------------',fgcolor=color.blue, expand='HORIZONTAL'}
local image_Mess = iup.label{title='----------------Marriage Certificate File--------------------',fgcolor=color.blue, expand='HORIZONTAL'}



function main()
local iniDir=fhGetContextInfo("CI_PROJECT_DATA_FOLDER").."\\Plugin Data\\"
	-- check that plugin data folder exists
	if not fhfu.folderExists(iniDir) then
		fhfu.createFolder (iniDir)
	end

 	iniFile=fhGetContextInfo("CI_PROJECT_DATA_FOLDER").."\\Plugin Data\\"..fhGetContextInfo("CI_PLUGIN_NAME")..".dat"
	hasUsed=fhGetIniFileValue(iniFile, "Defaults", "Welcome", "text") 
	dlgSize=fhGetIniFileValue(iniFile, "Defaults", "Dialog Size", "text",dlgSize) 

	-- do I need to display welcome screen
	if hasUsed~="Yes" then
			Welcome()
	end

local ptrParents=fhNewItemPtr();

	templateID=FindTemplate('Civil Registration Certificate')

	-- check that template exists
	if templateID~=-1 then
		--Try to get the Current Individual, Can't continue if not selected
		currPtr=fh.getCurrentIndividual ()
		if currPtr:IsNotNull() then
			curPerson=fhGetItemText(currPtr,'INDI.NAME:GIVEN_ALL')..' '
			curDates=fhGetItemText(currPtr,'INDI.BIRT.DATE')..' - '..fhGetItemText(currPtr,'INDI.DEAT.DATE')
			-- get husband and wife

			ptrFamily:MoveTo(currPtr, "INDI.FAMS")
			if ptrFamily:IsNull() then
				fhMessageBox("No marriage exists for person selected")
			else
				ptrFamily = fhGetValueAsLink(ptrFamily)
				husbandPtr=fhGetValueAsLink(fhGetItemPtr(ptrFamily,'~.HUSB'))
				wifePtr=fhGetValueAsLink(fhGetItemPtr(ptrFamily,'~.WIFE'))
				curPerson=curPerson..fhGetItemText(husbandPtr,'INDI.NAME:SURNAME')
				-- get fathers
				if husbandPtr:IsNotNull() then
					ptrParents:MoveTo(husbandPtr, "INDI.FAMC")
					if ptrParents:IsNotNull() then				
						ptrParents = fhGetValueAsLink(ptrParents)
						husbandFatherPtr=fhGetValueAsLink(fhGetItemPtr(ptrParents,'~.HUSB'))
					end
				end
				if wifePtr:IsNotNull() then
					ptrParents:MoveTo(wifePtr, "INDI.FAMC")
					if ptrParents:IsNotNull() then				
						ptrParents = fhGetValueAsLink(ptrParents)
						wifeFatherPtr=fhGetValueAsLink(fhGetItemPtr(ptrParents,'~.HUSB'))
					end
				end
				-- show dialog screen
				MainDialog()
			end

		else
 			fhMessageBox("No person selected")
		end		
	else
 		fhMessageBox("Requires Civil Registration Index source template from the Essentials collection")
	end


end

-------------------------------------------------------
-- identify media record
-------------------------------------------------------
function btn_Media:action() 
    res=self.name
		-- validate form
    selFile=ListFiles(inp_img_dir.value)
		inp_file_list.title = selFile
  		ShowFilename()
end

-------------------------------------------------------
-- Select media directory from window dialog
-------------------------------------------------------
function btn_mediaDir:action()
local dirName=""

    res=self.name
		-- get directory name
		dirName=ListDir(true)
		if dirName~="" then
			inp_Media_dir.value=dirName
		end
end

-------------------------------------------------------
-- Select image directory from window dialog
-------------------------------------------------------
function btn_imageDir:action()
local dirName=""

    res=self.name
		-- get directory name
		dirName=ListDir(false)
		if dirName~="" then
			inp_img_dir.value=dirName
		end
end


-------------------------------------------------------
-- show help screen
-------------------------------------------------------
function btn_help:action()
	Help()
end


function lst_name:action(t,i,v)

	if i==3 then
		inp_name.readonly="NO"
	else	
		inp_name.readonly="YES"
	end
  ShowFilename()
end

-------------------------------------------------------
-- validate dialog and close screen if valid 
-------------------------------------------------------
function bt_Save:action() 
	-- validate
	lab_message.title = ''
		-- validate form
		if lst_Region.value=='0' then
			lab_message.title = 'No region selected'
			lst_Region.bgcolor = color.error
		elseif lst_MarriagePlace.value=='' then
			lab_message.title = 'No Marriage Place selected'
			lst_MarriagePlace.bgcolor = color.error
		elseif IsToday(dte_Marriagedate) then
			lab_message.title = 'No Marriage Date selected'
			dte_Marriagedate.bgcolor = color.error
		elseif txt_Registrar.value=='' then
			lab_message.title = 'No celebrant selected'
			txt_Registrar.bgcolor = color.error
		elseif inp_Media_dir.value=='' then
			lab_message.title = 'Please provide a media directory to copy file to'
			inp_Media_dir.bgcolor = color.error
		elseif inp_file_list.title=='' then
			lab_message.title = 'No media file selected'
			inp_file_list.bgcolor = color.error
		elseif inp_name.value=='' and inp_name.readonly=="NO" then
			lab_message.title = 'No filename for image file'
		elseif selFile=='' then
			lab_message.title = 'No image selected'
		else
			SaveEntry()	
		end

end

function btn_Cancel:action() 
local exit=true
	if btn_Cancel.title=="Cancel" then
		res = fhMessageBox("Do you wish to cancel saving this certificate?", "MB_YESNO", "MB_ICONQUESTION")
		if res == "No" then 
    		exit=false
			dlg:show()
		end 
	end

	if exit then 
		return iup.CLOSE  
	end

end


-- << insert additional functions here >>
----------------------------------------------------
-- Function: MainDialog - display dialog
--           This is the Main Dialog Screen
---------------------------------------------------
function MainDialog()
-------------------------------------------------------
-- Dialog field actions 
-------------------------------------------------------
function Exit()
	return iup.CLOSE
end


	dlg = iup.dialog{ 
            iup.vbox{ 
            		iup.hbox{iup.label{title='Region',size='140'},dte_Today,lst_Region},
               iup.hbox{iup.label{title='Repository',size='140'},lst_Repos},
               iup.hbox{iup.label{title='',size='140'},info_Mess},
							iup.scrollbox{ 
								iup.vbox{ 										
										iup.hbox{iup.label{title='Where Married',size='140'},txt_Address},
                     iup.hbox{iup.label{title='Marriage Place',size='140'}, lst_MarriagePlace},
 										iup.hbox{iup.label{title='Entry Number',size='140'},txt_EntryNo},
                     iup.hbox{iup.label{title='Date of Marriage',size='140'},dte_Marriagedate},
                     iup.hbox{iup.label{title=' ',size='140'},lab_husband,lab_wife,gap='5'},
                     iup.hbox{iup.label{title='Name and Surname',size='140'},txt_Husband,txt_Wife,gap='5'},
                     iup.hbox{iup.label{title='Age',size='140'},txt_HusbandAge,txt_WifeAge,gap='5'},
                     iup.hbox{iup.label{title='Condition',size='140'},lst_HusbandCond,lst_WifeCond,gap='5'},
                     iup.hbox{iup.label{title='Rank or Profession',size='140'},txt_HusbandOcc,txt_WifeOcc,gap='5'},
                     iup.hbox{iup.label{title='Residence at time of marriage',size='140'},txt_HusbandAddress,txt_WifeAddress,gap='5'},
                     iup.hbox{iup.label{title='Place',size='140'},lst_HusbandPlace,lst_WifePlace,gap='5'},
                     iup.hbox{iup.label{title='Fathers Name and Surname',size='140'},txt_HusbandFather,txt_WifeFather,gap='5'},
                     iup.hbox{iup.label{title='Rank or Profession (Father)',size='140'},txt_HusbandFatherOcc,txt_WifeFatherOcc,gap='5'},
                     iup.hbox{iup.label{title='Rites and Ceremonies ',size='140'}, txt_Rites},
                     iup.hbox{iup.label{title='Marriage Type',size='140'}, lst_MarriageType},
                     iup.hbox{iup.label{title='In the Prescence of',size='140'}, txt_Informant},
 										iup.hbox{iup.label{title='Married by',size='140'},txt_Registrar},
                     iup.hbox{iup.label{title='Assessment',size='140'}, lst_Assessment},
        					},
      						canfocus = "NO",
      						scrollbar = "VERTICAL",
      						xmax = "0",
      						dy = "334",
      						ymax = "583",
      						wheeldropfocus = "YES",
      						},
                     iup.hbox{iup.label{title='',size='140'},image_Mess},
                     iup.hbox{btn_imageDir,iup.label{title='',size='1'},inp_img_dir},
                     iup.hbox{btn_mediaDir,iup.label{title='',size='1'},inp_Media_dir},
										iup.hbox{iup.label{title=''},lst_name,size='70',inp_name,gap='25'},
										iup.hbox{btn_Media,lab_Mess},
                     iup.hbox{iup.label{title='Image File',size='70'}, inp_file_list},
										iup.hbox{iup.label{title=' ',size='140'}},
                     iup.label{title='Status'},lab_message,
                     iup.hbox{bt_Save,iup.label{title='',size='120'},btn_Cancel,iup.label{title='',size='120'},btn_help;padding='10',gap='5'}
                     ;padding='5',gap='5',nmargin='20x20'
                     },
             title='Add Marriage Certificate for '..curPerson..' '..curDates,padding='10',gap='5', size='500',
             close_cb=function() res='closed' return iup.CLOSE end}

						-- populate list with defaults
						PopulateList(lst_Region,GetRegions(templateID))
						PopulateList(lst_Repos,GetRepositories())
						PopulateList(lst_MarriagePlace,fh.createPlaceList())
						PopulateList(lst_HusbandPlace,fh.createPlaceList())
						PopulateList(lst_WifePlace,fh.createPlaceList())
						ListCondition(lst_HusbandCond)
						ListCondition(lst_WifeCond)
						ListAssessments(lst_Assessment)
						ListMarriageTypes(lst_MarriageType)


						inp_file_list.title=''
						-- populate name 
						txt_Husband.value=fhGetItemText(husbandPtr,'INDI.NAME')
						txt_Wife.value=fhGetItemText(wifePtr,'INDI.NAME')
						txt_HusbandFather.value=fhGetItemText(husbandFatherPtr,'INDI.NAME')
						txt_WifeFather.value=fhGetItemText(wifeFatherPtr,'INDI.NAME')

						-- read ini file and populate accordingly
						inp_img_dir.value=fhGetIniFileValue(iniFile, "Directories", "Images", "text") 
						inp_Media_dir.value=fhGetIniFileValue(iniFile, "Directories", "Marriage", "text") 
						region=fhGetIniFileValue(iniFile, "Defaults", "Region", "text","") 		
						lst_Region.valuestring=region
						repos=fhGetIniFileValue(iniFile, region, "Repository", "text",'') 			
						lst_Repos.valuestring=repos
						assessment=fhGetIniFileValue(iniFile, region, "Assessment", "text",'') 			
						lst_Assessment.valuestring=assessment
	dlg:show()      -- Show the dialog
-- size the dialog screen appropriately
	dlg.size=dlgSize
	iup.Map(dlg)
	iup.MainLoop()  -- Process the dialog

	dlg:destroy()   -- Clean up the dialog, note all dialog controls are destroyed as well

end

----------------------------------------------------
-- Function: SaveEntry - Saves  ini file settings
--           and creates citation
--           then updates events and saves citation
---------------------------------------------------
function SaveEntry()
local txt=""
local region=lst_Region.valuestring
local ptrLink =fhNewItemPtr();
local ptrMarriage=fhNewItemPtr();
local ptrTextFromSource=fhNewItemPtr();
local ptrInfo =fhNewItemPtr();
local dtMyDate,yr

	-- save ini file settings
	fhSetIniFileValue(iniFile, "Defaults", "Dialog Size", "text",dlg.Size)
	fhSetIniFileValue(iniFile, "Defaults", "Region", "text",region)
	fhSetIniFileValue(iniFile,  region, "Assessment", "text",lst_Assessment.valuestring)
	fhSetIniFileValue(iniFile,  region, "Repository", "text",lst_Repos.valuestring)
	if CheckDirectory(inp_img_dir.value) then
		fhSetIniFileValue(iniFile, "Directories", "Images", "text",inp_img_dir.value)
	end
	if CheckDirectory(inp_Media_dir.value) then
		fhSetIniFileValue(iniFile, "Directories", "Marriage", "text",inp_Media_dir.value)
	end

		lab_message.title = ''
		-- create a templated source record
		sourcePtr= fhCreateItem("SOUR")
		ptrLink = fhCreateItem("_SRCT", sourcePtr) 
		fhSetValueAsLink(ptrLink, ptrTemplate)
		ptrLink = fhCreateItem("TITL", sourcePtr)
		-- add fields
		AddInfo(sourcePtr, '~NM-PRINCIPAL', txt_Husband.value)
		AddInfo(sourcePtr, '~NM-PRINCIPAL_2', txt_Wife.value)
		AddInfo(sourcePtr, '~EN-TYPE', 'Marriage')
		AddInfo(sourcePtr, '~EN-REGION', region)
		dtMyDate,yr=CalenderToDate(dte_Marriagedate.value)  
		AddInfoAsDate(sourcePtr, '~DT-DATE', dtMyDate)
		AddInfo(sourcePtr, '~PL-LOCATION', lst_MarriagePlace.value)
		AddInfo(sourcePtr, '~AD-ADDRESS', txt_Address.value)
		AddInfo(sourcePtr, '~TX-REFERENCE', txt_EntryNo.value)
		if lst_Repos.valuestring~=nil then
			ptrLink = fhCreateItem("~RP-REPOSITORY", sourcePtr) 
			fhSetValueAsLink(ptrLink, FindRepository(lst_Repos.valuestring))
		end
		AddInfo(sourcePtr, 'TITL', 'Marriage certificate of '..fhGetItemText(sourcePtr,'~.~NM-PRINCIPAL')..' and '..fhGetItemText(sourcePtr,'~.~NM-PRINCIPAL_2')..', '..fhGetItemText(sourcePtr,'~.~DT-DATE'))

		-- Create text from Source
		AddTextFromSource(sourcePtr)

		-- Add Media Record
		if inp_name.value=='' then
			inp_name.value=fhGetItemText(sourcePtr,'~.TITL')
		end
		filename= SaveImageFile(inp_file_list.title,inp_Media_dir.value, inp_name.value) 
		if filename~="" then
			-- copied file , now create object
			if CreateObject(sourcePtr,filename) ==false then
				lab_message.title = 'Failed to link image to source'
			end
		else
			lab_message.title = 'Failed to copy file'
		end
		-- create / update Marriage event
		ptrMarriage=fhGetItemPtr(ptrFamily,'~.MARR')
		if ptrMarriage:IsNull() then
			-- create Marriage Event
			ptrMarriage = fhCreateItem("MARR", ptrFamily)
		end
		AddInfo(ptrMarriage, 'DATE', fhGetItemText(sourcePtr,'~.~DT-DATE'))
		AddInfo(ptrMarriage, "PLAC", lst_MarriagePlace.value) 
		AddInfo(ptrMarriage, "ADDR", txt_Address.value)
    	ptrLink = fhCreateItem("SOUR", ptrMarriage) 
		AddInfo(ptrLink, 'QUAY', lst_Assessment.valuestring)
    	fhSetValueAsLink(ptrLink, sourcePtr)
		-- add ages
		if txt_HusbandAge.value ~="" then
			ptrInfo= fhCreateItem("HUSB", ptrMarriage)
			AddInfo(ptrInfo, 'AGE', txt_HusbandAge.value)
		end
		if txt_WifeAge.value ~="" then
			ptrInfo= fhCreateItem("WIFE", ptrMarriage)
			AddInfo(ptrInfo, 'AGE', txt_WifeAge.value)
		end
		-- add witness
		if txt_Informant.value ~="" then
			local sWitness=Split(txt_Informant.value,'\n')
			for i,v in ipairs(sWitness) do
				if v ~="" then
					ptrInfo= fhCreateItem("_SHAN", ptrMarriage)
					fhSetValueAsText(ptrInfo, v) 
					AddInfo(ptrInfo, 'ROLE', "Witness")
	    			ptrLink = fhCreateItem("SOUR", ptrInfo) 
					AddInfo(ptrLink, 'QUAY', lst_Assessment.valuestring)
					fhSetValueAsLink(ptrLink, sourcePtr)
				end
			end
		end

		-- Add Residence
		if lst_HusbandPlace.value ~="" then
			ptrOcc=fhCreateItem("RESI", husbandPtr)
			AddInfo(ptrOcc, "ADDR", txt_HusbandAddress.value)
			AddInfo(ptrOcc, "PLAC", lst_HusbandPlace.value)
			AddInfoAsDate(ptrOcc, "DATE", dtMyDate)
			if txt_HusbandAge.value ~="" then
				AddInfo(ptrOcc, 'AGE', txt_HusbandAge.value)
			end
    		ptrLink = fhCreateItem("SOUR", ptrOcc) 
			AddInfo(ptrLink, 'QUAY', lst_Assessment.valuestring)
    		fhSetValueAsLink(ptrLink, sourcePtr)
		end

		if lst_WifePlace.value ~="" then
			ptrOcc=fhCreateItem("RESI", wifePtr)
			AddInfo(ptrOcc, "ADDR", txt_WifeAddress.value)
			AddInfo(ptrOcc, "PLAC", lst_WifePlace.value)
			AddInfoAsDate(ptrOcc, "DATE", dtMyDate)
			if txt_WifeAge.value ~="" then
				AddInfo(ptrOcc, 'AGE', txt_WifeAge.value)
			end
    		ptrLink = fhCreateItem("SOUR", ptrOcc) 
			AddInfo(ptrLink, 'QUAY', lst_Assessment.valuestring)
    		fhSetValueAsLink(ptrLink, sourcePtr)
		end

		-- add occupation(s)
		if txt_HusbandOcc.value ~="" then
			ptrOcc=fhCreateItem("OCCU", husbandPtr)
			fhSetValueAsText(ptrOcc, txt_HusbandOcc.value)
			AddInfoAsDate(ptrOcc, "DATE", dtMyDate)
			if txt_HusbandAge.value ~="" then
				AddInfo(ptrOcc, 'AGE', txt_HusbandAge.value)
			end
    		ptrLink = fhCreateItem("SOUR", ptrOcc) 
			AddInfo(ptrLink, 'QUAY', lst_Assessment.valuestring)
    		fhSetValueAsLink(ptrLink, sourcePtr)
		end


		if txt_WifeOcc.value ~="" then
			ptrOcc=fhCreateItem("OCCU", wifePtr)
			fhSetValueAsText(ptrOcc, txt_WifeOcc.value)
			AddInfoAsDate(ptrOcc, "DATE", dtMyDate)
			if txt_WifeAge.value ~="" then
				AddInfo(ptrOcc, 'AGE', txt_WifeAge.value)
			end
    		ptrLink = fhCreateItem("SOUR", ptrOcc) 
			AddInfo(ptrLink, 'QUAY', lst_Assessment.valuestring)
    		fhSetValueAsLink(ptrLink, sourcePtr)
		end

		if txt_HusbandFatherOcc.value ~="" then
			ptrOcc=fhCreateItem("OCCU", husbandFatherPtr)
			fhSetValueAsText(ptrOcc, txt_HusbandFatherOcc.value)
			AddInfoAsDate(ptrOcc, "DATE", dtMyDate)
    		ptrLink = fhCreateItem("SOUR", ptrOcc) 
			AddInfo(ptrLink, 'QUAY', lst_Assessment.valuestring)
    		fhSetValueAsLink(ptrLink, sourcePtr)
		end

		if txt_WifeFatherOcc.value ~="" then
			ptrOcc=fhCreateItem("OCCU", wifeFatherPtr)
			fhSetValueAsText(ptrOcc, txt_WifeFatherOcc.value)
			AddInfoAsDate(ptrOcc, "DATE", dtMyDate)
    		ptrLink = fhCreateItem("SOUR", ptrOcc) 
			AddInfo(ptrLink, 'QUAY', lst_Assessment.valuestring)
    		fhSetValueAsLink(ptrLink, sourcePtr)
		end

		if lab_message.title == '' then
			lab_message.fgcolor=color.green
			lab_message.title = 'Marriage Certificate Saved'
			btn_Cancel.title="Exit"
		end

end

-------------------------------------------------------
-- ListFiles - display windows file dialog screen to select image file
--						 - passes default directory to windows dialog screen
-------------------------------------------------------

function ListFiles(defDir)
	f, err = iup.GetFile(defDir.."/".."*.jpg;*.png")
	if err == 1 then
  		iup.Message("New file", f)
	elseif err == 0 then
  	return f
	end
end

function ListDir(projectDir)
	local dataDir=""
	if projectDir then dataDir=fhGetContextInfo("CI_PROJECT_DATA_FOLDER") end
	filedlg = iup.filedlg{directory=dataDir,dialogtype = "DIR", title = "Select Directory"}
	iup.Popup(filedlg)

	-- Gets file dialog status
	status = filedlg.status

	if status == "0" or status == "1" then 
  		return filedlg.value
	end
	return ""

end

----------------------------------------------------
-- Function: ShowFilename - display new filename to copy to
---------------------------------------------------
function ShowFilename()
local path=""
local file=""
local extension=""

		if lst_name.value=="1" then
			-- display source name
			inp_name.value=""
		elseif lst_name.value=="2" then
			path,file,extension = SplitFilename(selFile)
			inp_name.value=string.gsub(file,"."..extension, "")
		else
			-- allow to overwrite
		end

end

---------------------------------------
-- Function: SaveImageFile - copies source image file to media directory
--														renaming to source title if requested
--           returns filename to be written as object file
---------------------------------------
function SaveImageFile(fromFile,ToDirectory, Title) 
local path=""
local file=""
local extension=""
local toFile=""


	path,file,extension = SplitFilename(fromFile)
	if fhfu.fileExists (fromFile) then
		toFile=ToDirectory.."\\"..Title.."."..extension
		if fhfu.fileExists (toFile) then
			fhMessageBox("Source already has image added")
		else
			-- does directory exist
			if CheckDirectory(ToDirectory) then
				-- copy file to new directory changing its name
				if fhfu.copyFile (fromFile, toFile, false) then 
					return string.gsub(toFile, fhGetContextInfo("CI_PROJECT_DATA_FOLDER").."\\", "")
				end
			end
		end
	end
	return ""
end


---------------------------------------
-- Function: CreateObject - creates a new object 
--           and links to source record
---------------------------------------
function CreateObject(SourcePtr,Filename)
local ptrObj= fhNewItemPtr()
local ptrFile= fhNewItemPtr()
local ptrLink= fhNewItemPtr()
local newFile=""

	if SourcePtr:IsNotNull() then
		-- now create object

		ptrObj= fhCreateItem("OBJE")
		ptrFile= fhCreateItem("FILE", ptrObj,true)
		if ptrFile:IsNotNull() then
			fhSetValueAsText(ptrFile,Filename)
			AddInfo(ptrFile,"FORM",PictureFormat(Filename))
			AddInfo(ptrFile,"TITL",fhGetItemText(SourcePtr,"SOUR.TITL"))
			AddInfo(ptrObj,"_DATE",fhGetItemText(SourcePtr,"SOUR.~DT-DATE"))
			AddInfo(ptrObj,"_KEYS","Picture")
			-- now link object to source
			ptrLink = fhCreateItem("OBJE", SourcePtr )             -- create a OBJE field 
			fhSetValueAsLink(ptrLink, ptrObj) 
		return true 
		end   	
	end
	return false
end

---------------------------------------
-- Function: AddTextFromSource 
--           add source text in rich text format
---------------------------------------
function AddTextFromSource(SourcePtr)
local txt='Certified Copy of an Entry of Marriage\n'
local txtPtr=fhNewItemPtr();
local dtMyDate,yr=CalenderToDate(dte_Marriagedate.value)  
local district=Split(lst_MarriagePlace.value ,',')
local rt = fhNewRichText()			  -- create a new rich text object

	txt=txt..yr..' Marriage solemnized at '..txt_Address.value..'\n'
	txt=txt..'of '..district[1]..' in the county of '..district[2]..'\n\n'
	txt=txt..''

	txt=txt..' No. | When Married. | Name and Surname. |  Age. | Condition. | Rank or Profession. | Residence at the time of Marriage. | Fathers Name and  Surname.  | Rank or Profession of Father. \n'

	txt=txt..'  '
	txt=txt..' | '
	txt=txt..' | '
	txt=txt..txt_Husband.value..' | '
	txt=txt..txt_HusbandAge.value..' | '
	txt=txt..lst_HusbandCond.valuestring..' | '
	txt=txt..txt_HusbandOcc.value..' | '
	txt=txt..txt_HusbandAddress.value..'\n'..lst_HusbandPlace.value..' | '
	txt=txt..txt_HusbandFather.value..' | '
	txt=txt..txt_HusbandFatherOcc.value..' | '
	txt=txt..''
	txt=txt..'  '
	txt=txt..txt_EntryNo.value..' | '
	txt=txt..dtMyDate:GetDisplayText()..' | '
	txt=txt..txt_Wife.value..' | '
	txt=txt..txt_WifeAge.value..' | '
	txt=txt..lst_WifeCond.valuestring..' | '
	txt=txt..txt_WifeOcc.value..' | '
	txt=txt..txt_WifeAddress.value..'\n'..lst_WifePlace.value..' | '
	txt=txt..txt_WifeFather.value..' | '
	txt=txt..txt_WifeFatherOcc.value..' | '
	txt=txt..''
	txt=txt..''

	txt=txt..'\nMarried in the '..txt_Address.value..' according to the Rites and Ceremonies of the '..txt_Rites.value..' '..lst_MarriageType.valuestring..' by me.\n'
	txt=txt..''
	txt=txt..''
	txt=txt..'This marriage was solemnized between us, | '..txt_Husband.value..'\n'..txt_Wife.value..' | '
	txt=txt..' In the presence of us, | '..txt_Informant.value..' | '
	txt=txt..txt_Registrar.value
	txt=txt..''
	txt=txt..''

	txtPtr= fhCreateItem("TEXT", sourcePtr)

	rt:SetText(txt)
	fhSetValueAsRichText(txtPtr , rt)  -- set value of the note using passed in parameter


end


---------------------------------------
-- Function: AddInfo - add info to fact, updating if fact exists
---------------------------------------
function AddInfo(ptr, Type, Value)
	
	if Type and Value then
		local infoPtr=fhGetItemPtr(ptr,'~.'..Type)
		if infoPtr:IsNull() then
			infoPtr= fhCreateItem(Type, ptr)
		end
		fhSetValueAsText(infoPtr, Value)
	end
end

---------------------------------------
-- Function: AddInfoAsDate - add info to fact, updating if fact exists
---------------------------------------
function AddInfoAsDate(ptr, Type, Value)
	
	if Type and Value then
		local infoPtr=fhGetItemPtr(ptr,'~.'..Type)
		if infoPtr:IsNull() then
			infoPtr= fhCreateItem(Type, ptr)
		end
		fhSetValueAsDate(infoPtr, Value)
	end
end


---------------------------------------------------
--function FindTemplate(template name)
-------------------------------------------------------
function FindTemplate(tName)
local ptr=fhNewItemPtr();


	ptr:MoveToFirstRecord('_SRCT')
	while ptr:IsNotNull() do
		if fhGetItemText(ptr,'~.NAME')==tName then
			ptrTemplate=ptr
			return fhGetRecordId(ptr)
		end
		ptr:MoveNext()
	end
	return -1
end

---------------------------------------------------
--function FindRepository(Repository name)
-------------------------------------------------------
function FindRepository(tName)
local ptr=fhNewItemPtr();


	ptr:MoveToFirstRecord('REPO')
	while ptr:IsNotNull() do
		if fhGetItemText(ptr,'~.NAME')==tName then
			ptrRepos=ptr
			return ptr
		end
		ptr:MoveNext()
	end
	return -1
end

---------------------------------------
-- Function: GetRegions - returns table of regions for CRI
---------------------------------------
function GetRegions(ID)

local array={}
local ptrTemplate = fhNewItemPtr()  -- declare pointer
local ptrFDef=fhNewItemPtr()
local strRegions=""

	ptrTemplate:MoveToFirstRecord("_SRCT")    -- point to the first record
	while not ptrTemplate:IsNull() do
  	 	if fhGetRecordId(ptrTemplate)==ID then
			-- loop through regions
			ptrFDef=	fhGetItemPtr(ptrTemplate,'~.FDEF')
			while not ptrFDef:IsNull() do
				if fhGetItemText(ptrFDef,'~.NAME')=='Region' then
					-- found field definiton
					strRegions =fhGetItemText(ptrFDef,'~.PROM')
					return Split(strRegions,'|')
				end
				ptrFDef:MoveNext('SAME_TAG')
			end
		end
   	ptrTemplate:MoveNext()
	end

	return array
end

---------------------------------------
-- Function: GetRepositories - returns table of repositories
---------------------------------------
function GetRepositories()

local array={}
local ptrRepos = fhNewItemPtr()  -- declare pointer
local strRepos=""

	ptrRepos:MoveToFirstRecord("REPO")    -- point to the first record
	while not ptrRepos:IsNull() do
   	-- For each Repository Add the Name to the list
  	 	strRepos =fhGetItemText(ptrRepos,'REPO.NAME')
		table.insert(array,strRepos )
   	ptrRepos:MoveNext()
	end

	return array
end

---------------------------------------
-- Function: ListAssessment - returns table of regions for CRI
---------------------------------------
function ListAssessments(l)
local array ={}
	
	table.insert(array,'Unreliable')
	table.insert(array,'Questionable')
	table.insert(array,'Secondary information')
	table.insert(array,'Primary information')
	table.insert(array,'')
	PopulateList(l,array)

end

---------------------------------------
-- Function: ListCondition - returns table of Marriage Status
---------------------------------------
function ListCondition(l)
local array ={}
	
	table.insert(array,'Bachelor')
	table.insert(array,'Spinster')
	table.insert(array,'Widower')
	table.insert(array,'Widow')
	table.insert(array,'Divorced')
	PopulateList(l,array)

end


---------------------------------------
-- Function: ListMarriageTypes - returns table of Marriage Types
---------------------------------------
function ListMarriageTypes(l)
local array ={}
	
	table.insert(array,'After Banns')
	table.insert(array,'By Banns')
	table.insert(array,'By Certificate')
	table.insert(array,'By Declaration')
	table.insert(array,'By Licence')
	table.insert(array,'By Publication')
	PopulateList(l,array)

end

-------------------------------------------------------
-- PopulateList populates a dialog list with table values
-------------------------------------------------------

function PopulateList(l, tblVals)
   local is_indexed = (rawget( tblVals, 1 ) ~= nil)
   l.REMOVEITEM = "ALL"
   if not is_indexed then
      local i=1
      for k, _ in pairs(tblVals) do
         l[tostring(i)]=k
         i=i+1
       end
   else
       for i, v in ipairs(tblVals) do
          l[tostring(i)]=v
       end	
   end
end


---------------------------------------
-- Function: Split - splits a string by delimter
--										and returns a table of strings
---------------------------------------
function Split(s, delimiter)
    result = {};
    for match in (s..delimiter):gmatch("(.-)"..delimiter) do
        table.insert(result, trim(match));
    end
    return result;
end

---------------------------------------
-- Function: trim - removes leading and trailing spaces
---------------------------------------
function trim(s)
  return (string.gsub(s, "^%s*(.-)%s*$", "%1"))
end

---------------------------------------
-- Function: PictureFormat - returns format of image file
---------------------------------------
function PictureFormat(fromFile)
local path=""
local file=""
local extension=""


	path,file,extension = SplitFilename(fromFile)  
	if string.upper(extension)=='JPG' then
		return 'jpeg'
	elseif string.upper(extension)=='TIF' then
		return 'tiff'
  	end
	return extension
end

---------------------------------------
-- Function: SplitFilename - Path, Filename, and Extension as 3 values
---------------------------------------
function SplitFilename(strFilename)
  -- Returns the Path, Filename, and Extension as 3 values
  if lfs.attributes(strFilename,"mode") == "directory" then
    local strPath = strFilename:gsub("[\\/]$","")
    return strPath.."\\","",""
  end
  strFilename = strFilename.."."
  return strFilename:match("^(.-)([^\\/]-%.([^\\/%.]-))%.?$")
end

---------------------------------------
-- Function: CheckDirectory - checks that directory exits
---------------------------------------
function CheckDirectory(Directory)
	if fhfu.folderExists(Directory) then 
		return true
	else
		-- create folder
		if fhfu.createFolder(Directory) then
			return true
		end
	end
	return false
end

---------------------------------------
-- Function: trims a sting; 
--           ensures that files do not have leading or trailing spaces
---------------------------------------
function trim(s)
  return (string.gsub(s, "^%s*(.-)%s*$", "%1"))
end


---------------------------------------
-- Function: CalenderToDate returns a FH date from Calender date 
--           yr is year from Calender date
---------------------------------------
function CalenderToDate(dteCal)
	local dt=Split(dteCal,'/')
	return fhNewDate(dt[1],dt[2],dt[3]),tostring(dt[1])  
end

---------------------------------------
-- Function: IsToday checks Calender date for today
---------------------------------------
function IsToday(dteCal)
	return dte_Today.value==dteCal.value
end

function tablelength(T)
   local count = 0
   for _ in pairs(T) do count = count + 1 end
   return count
end

-------------------------------------------------------
-- Welcome Dialog
-------------------------------------------------------
local chk_hasUsed=iup.toggle{title = "Show this screen next time", value="OFF"}
local btn_OKW = iup.button{name='ok', title='OK'}
local btn_welHelp = iup.button {name='welHelp', title='Help'}

function chk_hasUsed:action(t,i,v)
		if t==1 then 
			hasUsed="No"
		else
			hasUsed="Yes"
		end
end

function btn_OKW:action() 
	res=self.name 
	return iup.CLOSE 
end

function btn_welHelp:action()
	Help()
end


---------------------------------------------------
--Welcome Dialog
-------------------------------------------------------
function Welcome()
		hasUsed="Yes"
		txt="Thank you for using "..plugIn .."\n\n"

		txt=txt.."To cater for different screen resolutions, there is a scroll bar for the certificate details\n"
		txt=txt.."By changing the size of the form (dragging down at the bottom of the form), more fields\n"
		txt=txt.."will become visible until the scroll bar disappears (if your screen supports that resolution)\n"
		txt=txt.."When you save a certificate, the current resolution of the screen is preserved for next time you run.\n"
		txt=txt..""
		txt=txt.."Help for this plugin can be shown by pressing the help key in the main (or this) screen\n\n"
		txt=txt.."For further support please post in the relevant Family Historian User Group forum\n"
		txt=txt.."or on the page for this plugin in the Family Historian store\n"
		txt=txt.."where the plugin author will answer your questions\n"
 		txt=txt.."which can be supplemented with screenshots if necessary.\n\n"
		txt=txt.."Links to these sites are available on the help page for this plugin\n\n"


		dlgWel = iup.dialog{ 
      	iup.vbox{ 
      iup.hbox{iup.label{title=txt}},
 			iup.hbox{iup.label{title=''},chk_hasUsed},
      iup.hbox{btn_OKW,btn_welHelp;padding='10',gap='5'}
         ;padding='5',gap='5',nmargin='20x20'
 			},
      title=plugIn..' welcome ',padding='10',gap='5', size='500',
      close_cb=function() res='closed' return iup.CLOSE end}
		dlgWel:show()      -- Show the dialog
		iup.MainLoop()  -- Process the dialog

		dlgWel:destroy()   -- Clean up the dialog, note all dialog controls are destroyed as well
		fhSetIniFileValue(iniFile, "Defaults", "Welcome", "text",hasUsed)

end

---------------------------------------------------
--Help Dialog
-------------------------------------------------------

local btn_OKH = iup.button{name='ok', title='OK'}

function btn_OKH:action() 
	res=self.name 
	return iup.CLOSE 
end

function Help()
	local txt_Help = iup.text{name='Help',expand='HORIZONTAL',multiline='YES',visiblelines=20}
	txt_Help.value=	help
	local txt="For further support please post in the relevant Family Historian User Group forum or on the page for this plugin in the Family Historian store\n"
		txt=txt.."where the plugin author will answer your questions which can be supplemented with screenshots if necessary."
		txt=txt.."Links to these sites are below\n\n"

	local resHelp="To cater for different screen resolutions, there is a scroll bar for the certificate details\n"
		resHelp=resHelp.."By changing the size of the form (dragging down at the bottom of the form), more fields\n"
		resHelp=resHelp.."will become visible until the scroll bar disappears (if your screen supports that resolution)\n"
		resHelp=resHelp.."When you save a certificate, the current resolution of the screen is preserved for the next time you run.\n"

		dlgHelp = iup.dialog{ 
      	iup.vbox{ 
 			iup.hbox{iup.label{title='',size='10'},txt_Help},
      iup.hbox{iup.label{title=''}},
			iup.hbox{iup.label{title=resHelp}},
			iup.hbox{iup.link{url="https://pluginstore.family-historian.co.uk/page/plugin/Simple-Marriage-Certificate",title="A User Guide may be available in Family Historian Plugin store"}},
			iup.hbox{iup.label{title=txt}},
			iup.hbox{iup.link{url="https://pluginstore.family-historian.co.uk/page/plugin/Simple-Marriage-Certificate",title="Family Historian Plugin store"}},
			iup.hbox{iup.link{url="https://www.fhug.org.uk/forum/viewtopic.php?t=22976",title="Family Historian User Group forums"}},
      iup.hbox{btn_OKH;padding='10',gap='5'}
         ;padding='5',gap='5',nmargin='20x20'

 			},
      title=plugIn..' help ',padding='10',gap='5', size='500',
      close_cb=function() res='closed' return iup.CLOSE end}
		dlgHelp:show()      -- Show the dialog
		iup.MainLoop()  -- Process the dialog

		dlgHelp:destroy()   -- Clean up the dialog, note all dialog controls are destroyed as well

end


main();



Source:Simple-Marriage-Certificate-1.fh_lua