Change Any Fact Tag.fh_lua

--[[
@Title:			Change Any Fact Tag
@Type:				Standard
@Author:			Mike Tate
@Contributors:	
@Version:			3.2
@Keywords:		
@LastUpdated:	08 Jan 2021
@Licence:			This plugin is copyright (c) 2021 Mike Tate & 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:	Changes or deletes any Individual Fact or Family Fact or Record Tag.
@V3.2:				Project only Fact Sets; Delete iup_gui.History = " "; Correct tblControls={  [btnGetHelp]  Plugin Store";
@V3.1:				Updated library to Functions Prototypes v3.0; iup_gui.Balloon = "NO" for PlayOnLinux/Mac; FH V7 Lua 3.5 IUP 3.28; Plugin Store Help;
@V3.0:				Updated code for Fact Defs based on Export Gedcom File. Updated library module for safer IUP GUI. Fix bug with long Fact Name/Label and no Abbreviation.
@V2.9:				Add filter by record selection.
@V2.8:				Bug fix to cope with Fact Set files having no Hidden tag line at all.
@V2.7:				Bug fix in strstrParseFactItem() for Witness Role items, and elsewhere for  Standard events BAPM, CHR, ANUL, DIV.
@V2.6:				Add _PLACe filter match for EMIG/IMMIgration Events, and cater for GroupIndex.fhdata  and also  Facts.
@V2.5:				Cope with Custom Fact names same as Standard/Defined names in TblDictionary, Date filter matches DATE:LONG or DATE:COMPACT as in Property Box, StrDisplayData uses BuildDataRef, and latest library modules.
@V2.4:				Both ANSI FH V5 & UTF-8 FH V6 IUP 3.11.2 Unicode settings, File > Encoding=ANSI/UTF-8, iup.SetGlobal("UTF8MODE","YES"), HelpDialogue conditional ExpandChildren="YES/NO", RefreshDialogue NaturalSize,
@V2.4:				Updated GUI library module, better Tab tooltip, refresh toggle & droplist tooltips for XP, add BalloonToggle(), handle UTF-16 FactSet files, UTF-8 Filter values, and V6 Place Records.
@V2.3:				GUI improved tool tips, StrBlue eliminated, background=StrWhite, V5.0.2 fhCreateItem FILE/FORM update, Version in Store check, Version History help, new library modules, minor code revisions.
@V2.2:				Added the Interface Font option, Result Set of changes, Knowledge Base Help, V5.0.0.3/4 fixes, and GUI & code revisions.
@V2.1:				Added the View Log and Purge Log buttons to user interface.
@V2.0:				Tested with many UDF Facts & Tags, and revised user interface with Filter option.
@V1.1:				Copes with most UDF Fact anomalies and adds many minor user interface features.
					Any unconvertible data and error reports are both logged and saved in SOURce Notes.
@V1.0 Beta:		Supports the Standard Fact Set, any Custom Fact Sets, undefined Custom Facts, and UDF Facts.
]]

if fhGetAppVersion() > 5 then fhSetStringEncoding("UTF-8") end

--[[
@Title:			aa Library Functions Preamble
@Author:			Mike Tate
@Version:			3.0
@LastUpdated:	20 Sep 2020
@Description:	All the library functions prototype closures for Plugins.
]]

--[[
@Module:			+fh+stringx_v3
@Author:			Mike Tate
@Version:			3.0
@LastUpdated:	19 Sep 2020
@Description:	Extended string functions to supplement LUA string library.
@V3.0:				Function Prototype Closure version with Lua 5.1 & 5.3 comaptibility; Added inert(strTxt) function;
@V2.5:				Support FH V6 Encoding = UTF-8;
@V2.4:				Tolerant of integer & nil parameters just link match & gsub;
@V1.0:				Initial version.
]]

local function stringx_v3()

	local fh = {}									-- Local environment table

	-- Supply current file encoding format --
	function fh.encoding()
		if fhGetAppVersion() > 5 then return fhGetStringEncoding() end
		return "ANSI"
	end -- function encoding

	-- Split a string using "," or chosen separator --
	function fh.split(strTxt,strSep)
		local tblFields = {}
		local strPattern = string.format("([^%s]+)", strSep or ",")
		strTxt = tostring(strTxt or "")
		strTxt:gsub(strPattern, function(strField) tblFields[#tblFields+1] = strField end)
		return tblFields
	end -- function split

	-- Split a string into numbers using " " or "," or "x" separators	-- Any non-number remains as a string
	function fh.splitnumbers(strTxt)
		local tblNum = {}
		strTxt = tostring(strTxt or "")
		strTxt:gsub("([^ ,x]+)", function(strNum) tblNum[#tblNum+1] = tonumber(strNum) or strNum end)
		return tblNum
	end -- function splitnumbers

	local strMagic = "([%^%$%(%)%%%.%[%]%*%+%-%?])"							-- UTF-8 replacement for "(%W)"

	-- Hide magic pattern symbols	^ $ ( ) % . [ ] * + - ?
	function fh.plain(strTxt)
		-- Prefix every magic pattern character with a % escape character,
		-- where %% is the % escape, and %1 is the original character capture.
		strTxt = tostring(strTxt or ""):gsub(strMagic,"%%%1")
		return strTxt
	end -- function plain

	-- matches is plain text version of string.match()
	function fh.matches(strTxt,strFind,intInit)
		strFind = tostring(strFind or ""):gsub(strMagic,"%%%1")			-- Hide magic pattern symbols
		return tostring(strTxt or ""):match(strFind,tonumber(intInit))
	end -- function matches

	-- replace is plain text version of string.gsub()
	function fh.replace(strTxt,strOld,strNew,intNum)
		strOld = tostring(strOld or ""):gsub(strMagic,"%%%1")				-- Hide magic pattern symbols
		return tostring(strTxt or ""):gsub(strOld,function() return strNew end,tonumber(intNum))	-- Hide % capture symbols
	end -- function replace

	-- Hide % escape/capture symbols in replacement so they are inert
	function fh.inert(strTxt)
		strTxt = tostring(strTxt or ""):gsub("%%","%%%%")					-- Hide all % symbols
		return strTxt
	end -- function inert

	-- convert is pattern without captures version of string.gsub()
	function fh.convert(strTxt,strOld,strNew,intNum)
		return tostring(strTxt or ""):gsub(tostring(strOld or ""),function() return strNew end,tonumber(intNum))	-- Hide % capture symbols
	end -- function convert

	local dicUpper = { }
	local dicLower = { }
	local dicCaseX = { }
	-- ASCII unaccented letter translations for Upper, Lower, and Case Insensitive
	for intUpper = string.byte("A"), string.byte("Z") do
		local strUpper = string.char(intUpper)
		local strLower = string.char(intUpper - string.byte("A") + string.byte("a"))
		dicUpper[strLower] = strUpper
		dicLower[strUpper] = strLower
		local strCaseX = "["..strUpper..strLower.."]"
		dicCaseX[strLower] = strCaseX
		dicCaseX[strUpper] = strCaseX
	end

	-- Supply character length of ANSI text --
	function fh.length(strTxt)
		return string.len(strTxt or "")
	end -- function length

	-- Supply character substring of ANSI text --
	function fh.substring(strTxt,i,j)
		return string.sub(strTxt or "",i,j)
	end -- function substring

	-- Translate upper/lower case ANSI letters to pattern that matches both --
	function fh.caseless(strTxt)
		strTxt = tostring(strTxt or ""):gsub("[A-Za-z]",dicCaseX)
		return strTxt
	end -- function caseless

	if fh.encoding() == "UTF-8" then

		-- Supply character length of UTF-8 text --
		function fh.length(strTxt)
			isFlag = fhIsConversionLossFlagSet()
			strTxt = fhConvertUTF8toANSI(strTxt or "")
			fhSetConversionLossFlag(isFlag)
			return string.len(strTxt)
		end -- function length

		local strUTF8 = "([%z\1-\127\194-\244][\128-\191]*)"			-- Cater for Lua 5.1 %z or Lua 5.3 \0
		if fhGetAppVersion() > 6 then
			strUTF8 = "([\0-\127\194-\244][\128-\191]*)"
		end

		-- Supply character substring of UTF-8 text --
		function fh.substring(strTxt,i,j)
			local strSub = ""
			j = j or -1
			if j < 0 then j = j + length(strTxt) + 1 end
			if i < 0 then i = i + length(strTxt) + 1 end
			for strChr in string.gmatch(strTxt or "",strUTF8) do
				if j <= 0 then break end
				j = j - 1
				i = i - 1
				if i <= 0 then strSub = strSub..strChr end
			end
			return strSub
		end -- function substring

		-- Translate lower case to upper case UTF-8 letters --
		function fh.upper(strTxt)
			strTxt = tostring(strTxt or ""):gsub("([a-z\194-\244][\128-\191]*)",dicUpper)
			return strTxt
		end -- function upper

		-- Translate upper case to lower case UTF-8 letters --
		function fh.lower(strTxt)
			strTxt = tostring(strTxt or ""):gsub("([A-Z\194-\244][\128-\191]*)",dicLower)
			return strTxt
		end -- function lower

		-- Translate upper/lower case UTF-8 letters to pattern that matches both --
		function fh.caseless(strTxt)
			strTxt = tostring(strTxt or ""):gsub("([A-Za-z\194-\244][\128-\191]*)",dicCaseX)
			return strTxt
		end -- function caseless

		-- Following tables use ASCII numeric coding to be immune from ANSI/UTF-8 encoding --

		local arrPairs =	-- Upper & Lower case groups of UTF-8 letters with same prefix --
		{--	{ Prefix; Beg ; End ; Inc; Offset Upper > Lower };			-- These include all ANSI letters and many more
			{ "\195"; 0x80; 0x96;  1 ; 32 };								-- 195=0xC3  U+00C0 to  U+00D6 and  U+00E0 to  U+00F6
			{ "\195"; 0x98; 0x9E;  1 ; 32 };								-- 195=0xC3  U+00D8 to  U+00DE and  U+00F8 to  U+00FE
			{ "\196"; 0x80; 0xB6;  2 ;  1 };								-- 196=0xC4 A U+0100 to k U+0137 in pairs
			{ "\196"; 0xB9; 0xBD;  2 ;  1 };								-- 196=0xC4 L U+0139 to l U+013E in pairs
			{ "\197"; 0x81; 0x87;  2 ;  1 };								-- 197=0xC5 L U+0141 to n U+0148 in pairs
			{ "\197"; 0x8A; 0xB6;  2 ;  1 };								-- 197=0xC5 ? U+014A to y U+0177 in pairs
			{ "\197"; 0xB9; 0xBD;  2 ;  1 };								-- 197=0xC5 Z U+0179 to  U+017E in pairs
			{ "\198"; 0x82; 0x84;  2 ;  1 };								-- 198=0xC6 ?  U+0182 to ?  U+0185 in pairs
			-- Add more Unicode groups here as usage increases --
		}
		local dicPairs =	-- Upper v Lower case UTF-8 letters that don't fit groups above --
		{	[string.char(0xC4,0xBF)] = string.char(0xC5,0x80);			-- ? U+013F and ? U+0140
			[string.char(0xC5,0xB8)] = string.char(0xC3,0xBF);			--  U+0178 and  U+00FF
		}

		local intBeg1 = string.byte(string.sub("",1))
		local intBeg2 = string.byte(string.sub("",2))
		local intEnd1 = string.byte(string.sub("Z",1))
		local intEnd2 = string.byte(string.sub("Z",2))
	--	print(string.format("%#x %#x %#x %#x",intBeg1,intBeg2,intEnd1,intEnd2)) -- Useful to work out numeric coding

		-- Populate the UTF-8 letter translation dictionaries --
		for intGroup, tblGroup in ipairs ( arrPairs ) do				-- UTF-8 accented letter groups
			local strPrefix = tblGroup[1]
			for intUpper = tblGroup[2], tblGroup[3], tblGroup[4] do
				local strUpper = string.char(intUpper)
				local strLower = string.char(intUpper + tblGroup[5])
				local strCaseX = strPrefix.."["..strUpper..strLower.."]"
				strUpper = strPrefix..strUpper
				strLower = strPrefix..strLower
				dicUpper[strLower] = strUpper
				dicLower[strUpper] = strLower
				dicCaseX[strLower] = strCaseX
				dicCaseX[strUpper] = strCaseX
			end
		end
		for strUpper, strLower in pairs ( dicPairs ) do					-- UTF-8 accented letters where upper & lower have different prefix
			dicUpper[strLower] = strUpper
			dicLower[strUpper] = strLower
			local strCaseX = ""
			for intByte = 1, #strUpper do									-- Matches more than just the two letters, but can't do any better
				strCaseX = strCaseX.."["..strUpper:sub(intByte,intByte)..strLower:sub(intByte,intByte).."]"
			end
			dicCaseX[strLower] = strCaseX
			dicCaseX[strUpper] = strCaseX
		end

	end

	-- overload fh functions into string table
	for strIndex, anyValue in pairs(fh) do
		if type(anyValue) == "function" then
			string[strIndex] = anyValue
		end
	end

	return fh

end -- local function stringx_v3

local stringx = stringx_v3()													-- To access FH string extension module

--[[
@Module:			+fh+tablex_v3
@Author:			Mike Tate
@Version:			3.0
@LastUpdated:	25 Aug 2020
@Description:	A Table Load Save Module.
@V3.0:				Function Prototype Closure version.
@V1.2:				Added local definitions of _ to ensure nil gets returned on error.
@V1.1:				?
@V1.0:				Initial version 0.94 is Lua 5.1 compatible.
]]

local function tablex_v3()

	local fh = {}									-- Local environment table

	------------------------------------------------------ Start Table Load Save
	-- require "_tableloadsave"
	--[[
   Save Table to File/Stringtable
   Load Table from File/Stringtable
   v 0.94
   
   Lua 5.1 compatible
   
   Userdata and indices of these are not saved
   Functions are saved via string.dump, so make sure it has no upvalues
   References are saved
   ----------------------------------------------------
   table.save( table [, filename] )
   
   Saves a table so it can be called via the table.load function again
   table must a object of type 'table'
   filename is optional, and may be a string representing a filename or true/1
   
   table.save( table )
      on success: returns a string representing the table (stringtable)
      (uses a string as buffer, ideal for smaller tables)
   table.save( table, true or 1 )
      on success: returns a string representing the table (stringtable)
      (uses io.tmpfile() as buffer, ideal for bigger tables)
   table.save( table, "filename" )
      on success: returns 1
      (saves the table to file "filename")
   on failure: returns as second argument an error msg
   ----------------------------------------------------
   table.load( filename or stringtable )
   
   Loads a table that has been saved via the table.save function
   
   on success: returns a previously saved table
   on failure: returns as second argument an error msg
   ----------------------------------------------------
   
   chillcode, http://lua-users.org/wiki/SaveTableToFile
   Licensed under the same terms as Lua itself.
	]]--

   -- declare local variables
   --// exportstring( string )
   --// returns a "Lua" portable version of the string
   local function exportstring( s )
      s = string.format( "%q",s )
      -- to replace
      s = string.gsub( s,"\\\n","\\n" )
      s = string.gsub( s,"\r","\\r" )
      s = string.gsub( s,string.char(26),"\"..string.char(26)..\"" )
      return s
   end
--// The Save Function
function fh.save( tbl,filename )
   local charS,charE = "   ","\n"
   local file,err,_ 					-- V1.2 -- Added _
   -- create a pseudo file that writes to a string and return the string
   if not filename then
      file =  { write = function( self,newstr ) self.str = self.str..newstr end, str = "" }
      charS,charE = "",""
   -- write table to tmpfile
   elseif filename == true or filename == 1 then
      charS,charE,file = "","",io.tmpfile()
   -- write table to file
   -- use io.open here rather than io.output, since in windows when clicking on a file opened with io.output will create an error
   else
      file,err = io.open( filename, "w" )
      if err then return _,err end
   end
   -- initiate variables for save procedure
   local tables,lookup = { tbl },{ [tbl] = 1 }
   file:write( "return {"..charE )
   for idx,t in ipairs( tables ) do
      if filename and filename ~= true and filename ~= 1 then
         file:write( "-- Table: {"..idx.."}"..charE )
      end
      file:write( "{"..charE )
      local thandled = {}
      for i,v in ipairs( t ) do
         thandled[i] = true
         -- escape functions and userdata
         if type( v ) ~= "userdata" then
            -- only handle value
            if type( v ) == "table" then
               if not lookup[v] then
                  table.insert( tables, v )
                  lookup[v] = #tables
               end
               file:write( charS.."{"..lookup[v].."},"..charE )
            elseif type( v ) == "function" then
               file:write( charS.."loadstring("..exportstring(string.dump( v )).."),"..charE )
            else
               local value =  ( type( v ) == "string" and exportstring( v ) ) or tostring( v )
               file:write(  charS..value..","..charE )
            end
         end
      end
      for i,v in pairs( t ) do
         -- escape functions and userdata
         if (not thandled[i]) and type( v ) ~= "userdata" then
            -- handle index
            if type( i ) == "table" then
               if not lookup[i] then
                  table.insert( tables,i )
                  lookup[i] = #tables
               end
               file:write( charS.."[{"..lookup[i].."}]=" )
            else
               local index = ( type( i ) == "string" and "["..exportstring( i ).."]" ) or string.format( "[%d]",i )
               file:write( charS..index.."=" )
            end
            -- handle value
            if type( v ) == "table" then
               if not lookup[v] then
                  table.insert( tables,v )
                  lookup[v] = #tables
               end
               file:write( "{"..lookup[v].."},"..charE )
            elseif type( v ) == "function" then
               file:write( "loadstring("..exportstring(string.dump( v )).."),"..charE )
            else
               local value =  ( type( v ) == "string" and exportstring( v ) ) or tostring( v )
               file:write( value..","..charE )
            end
         end
      end
      file:write( "},"..charE )
   end
   file:write( "}" )
   -- Return Values
   -- return stringtable from string
   if not filename then
      -- set marker for stringtable
      return file.str.."--|"
   -- return stringttable from file
   elseif filename == true or filename == 1 then
      file:seek ( "set" )
      -- no need to close file, it gets closed and removed automatically
      -- set marker for stringtable
      return file:read( "*a" ).."--|"
   -- close file and return 1
   else
      file:close()
      return 1
   end
end

--// The Load Function
function fh.load( sfile )
   local tables,err,_ 					-- V1.2 -- Added _
   -- catch marker for stringtable
   if string.sub( sfile,-3,-1 ) == "--|" then
      tables,err = loadstring( sfile )
   else
      tables,err = loadfile( sfile )
   end
   if err then return _,err
   end
   tables = tables()
   for idx = 1,#tables do
      local tolinkv,tolinki = {},{}
      for i,v in pairs( tables[idx] ) do
         if type( v ) == "table" and tables[v[1]] then
            table.insert( tolinkv,{ i,tables[v[1]] } )
         end
         if type( i ) == "table" and tables[i[1]] then
            table.insert( tolinki,{ i,tables[i[1]] } )
         end
      end
      -- link values, first due to possible changes of indices
      for _,v in ipairs( tolinkv ) do
         tables[idx][v[1]] = v[2]
      end
      -- link indices
      for _,v in ipairs( tolinki ) do
         tables[idx][v[2]],tables[idx][v[1]] =  tables[idx][v[1]],nil
      end
   end
   return tables[1]
end

------------------------------------------------------ End Table Load Save

	-- overload fh functions into table
	for strIndex, anyValue in pairs(fh) do
		if type(anyValue) == "function" then
			table[strIndex] = anyValue
		end
	end

	return fh

end -- local function tablex_v3

local tablex  = tablex_v3 ()													-- To access FH table extension module

--[[
@Module:			+fh+iterate_v3
@Author:			Mike Tate
@Version:			3.0
@LastUpdated:	25 Aug 2020
@Description:	An iterater functions module to supplement LUA functions.
@V3.0:				Function Prototype Closure version.
@V1.2:				RecordTypes() includes HEAD tag.
@V1.1:				?
@V1.0:				Initial version.
]]

local function iterate_v3()

	local fh = {}																-- Local environment table

	-- Iterator for all records of one chosen type --
	function fh.Records(strType)
		local ptrAll = fhNewItemPtr()										-- Pointer to all records in turn
		local ptrRec = fhNewItemPtr()										-- Pointer to record returned to user
		ptrAll:MoveToFirstRecord(strType)
		return function ()
			ptrRec:MoveTo(ptrAll)
			ptrAll:MoveNext()
			if ptrRec:IsNotNull() then return ptrRec end
		end
	end -- function Records

	-- Iterator for all the record types --
	function fh.RecordTypes()
		local intNext = -1														-- Next record type number
		local intLast = fhGetRecordTypeCount()								-- Last record type number
		return function()
			intNext = intNext + 1
			if intNext == 0 then												-- Includes HEAD tag -- V1.2
				return "HEAD"
			elseif intNext <= intLast then
				return fhGetRecordTypeTag(intNext)							-- Return record type tag
			end
		end
	end -- function RecordTypes

	-- Iterator for all items in all records of chosen types --
	function fh.Items(...)
		local arg = {...}
		local intType = 1														-- Integer record type number
		local tblType = {}														-- Table of record type tags
		local ptrNext = fhNewItemPtr()										-- Pointer to next item in turn
		local ptrItem = fhNewItemPtr()										-- Pointer to item returned to user

		if #arg == 0 then
			for intType = 1, fhGetRecordTypeCount() do					-- No parameters so use all record types
				tblType[intType] = fhGetRecordTypeTag(intType)
			end
		else
			tblType = arg														-- Got parameters so use them instead
		end
	--	print(tblType[intType],intType)
		ptrNext:MoveToFirstRecord(tblType[intType])						-- Get first record of first type

		return function()
			repeat
				while ptrNext:IsNotNull() do									-- Loop through all items
					ptrItem:MoveTo(ptrNext)
					ptrNext:MoveNextSpecial()
					if ptrItem:IsNotNull() then return ptrItem end
				end
				intType = intType + 1											-- Loop through each record type
				if intType <= #tblType then
					ptrNext:MoveToFirstRecord(tblType[intType])
				end
			until intType > #tblType
		end
	end -- function Items

	-- Iterator for all facts of an individual --
	function fh.Facts(ptrIndi)
		local ptrItem = fhNewItemPtr()										-- Pointer to each item at level 1
		local ptrFact = fhNewItemPtr()										-- Pointer to each fact returned to user
		ptrItem:MoveToFirstChildItem(ptrIndi)
		return function ()
			while ptrItem:IsNotNull() do
				ptrFact:MoveTo(ptrItem)
				ptrItem:MoveNext()
				if fhIsFact(ptrFact) then return ptrFact end
			end
		end
	end -- function Facts

	return fh

end -- local function iterate_v3

local iterate = iterate_v3()													-- To access FH iterate items module

--[[
@Module:			+fh+general_v3
@Author:			Mike Tate
@Version:			3.0
@LastUpdated:	25 Aug 2020
@Description:	A general functions module to supplement LUA functions, where all filenames are in ANSI.
@V3.0:				Function Prototype Closure version; GetDayNumber() error message reasons;
@V1.5:				Revised SplitFilename(strFilename) for missing extension.
@V1.4:				Revised EstimatedBirthDates() & EstimatedDeathDates() to fix null Dates.
@V1.3:				Add GetDayNumber(), EstimatedBirthDates(), EstimatedDeathDates().
@V1.2:				SplitFilename() updated for directory only paths, and MakeFolder() added.
@V1.1:				pl.path experiment revoked. New DirTree with omit branch option. Avoid using stringx_v2.
@V1.0:				Initial version.
]]

local function general_v3()

	local fh = {}													-- Local environment table

	require "lfs"													-- To access LUA filing system

	if fhGetAppVersion() > 6 then
		unpack = table.unpack
	end

	-- Check if file exists --
	function fh.FlgFileExists(strFileName)
		return lfs.attributes(strFileName,"mode") == "file"
		--[=[
		if lfs.attributes(strFileName,"mode") == "file" then
			return true
		else
			return false
		end
		--]=]
	end -- function FlgFileExists

	-- Check if folder exists --
	function fh.FlgFolderExists(strFolderName)
		return lfs.attributes(strFolderName:gsub("\\$",""),"mode") == "directory"
		--[=[
		if lfs.attributes(strFolderName:gsub("\\$",""),"mode") == "directory" then
			return true
		else
			return false
		end
		--]=]
	end -- function FlgFolderExists

	-- Check if folder writable --
	function fh.FlgFolderWrite(strFolderName)
		if fh.FlgFolderExists(strFolderName) then
			local fileHandle, strError = io.open(strFolderName.."\\xyz.xyz","w")
			if fileHandle ~= nil then
				fileHandle:close()
				os.remove(strFolderName.."\\xyz.xyz")
				return true
			end
		end
		return false
	end -- function FlgFolderWrite

	-- Open File and return Handle --
	function fh.OpenFile(strFileName,strMode)
		local fileHandle, strError = io.open(strFileName,strMode)
		if fileHandle == nil then
			error("\n Unable to open file in \""..strMode.."\" mode. \n "..strFileName.." \n "..strError.." \n")
		end
		return fileHandle
	end -- function OpenFile

	-- Save string to file --
	function fh.SaveStringToFile(strString,strFileName)
		local fileHandle = fh.OpenFile(strFileName,"w")
		fileHandle:write(strString)
		assert(fileHandle:close())
	end -- function SaveStringToFile

	-- Load string from file --
	function fh.StrLoadFromFile(strFileName)
		local fileHandle = fh.OpenFile(strFileName,"r")
		local strString = fileHandle:read("*all")
		assert(fileHandle:close())
		return strString
	end -- function StrLoadFromFile

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

	-- Return a Directory Tree entry & attributes on each iteration --
	function fh.DirTree(strDir,...)
		local arg = {...}
		assert(strDir and strDir ~= "", "directory parameter is missing or empty")
		if strDir:sub(-1) == "/"
		or strDir:sub(-1) == "\\" then
			strDir = strDir:sub(1,-2)								-- Remove trailing "/" or "\"
		end
    
		local function doYieldTree(strDir)
			for strEntry in lfs.dir(strDir) do
				if strEntry ~= "." and strEntry ~= ".." then
					strEntry = strDir.."\\"..strEntry
					local tblAttr, strError = lfs.attributes(strEntry)
					if not tblAttr then tblAttr = { mode="attrfail"; error=strError; } end 
					coroutine.yield(strEntry,tblAttr)
					if tblAttr.mode == "directory" then
						local isOK = true
						for intOmit, strOmit in ipairs (arg) do
							if strEntry:matches(strOmit) then	-- Omit tree branch
								isOK = false
								break
							end
						end
						if isOK then doYieldTree(strEntry) end
					end
				end
			end
		end -- local function doYieldTree

		return coroutine.wrap(function() doYieldTree(strDir) end)
	end -- function DirTree

	local function strErrorText(strError,strFileName,intRepeat)
		return strError:gsub(strFileName:match("(.+\\).+"),"Del#"..tostring(intRepeat)..":")
	end -- local function strErrorText

	-- Delete file if it exists --
	function fh.DeleteFile(strFileName,errFunction)
		if fh.FlgFileExists(strFileName) then
			local fileHandle, strError = os.remove(strFileName)
			if fileHandle == nil then
				local intRepeat = 1
				repeat
					if intRepeat > 1 and type(errFunction) == "function" then
						errFunction(strErrorText(strError,strFileName,intRepeat))
					end
					fhSleep(300,100)
					if fh.FlgFileExists(strFileName) then
						fileHandle, strError = os.remove(strFileName)
					end
					intRepeat = intRepeat + 1
				until fileHandle ~= nil or intRepeat > 10
				if intRepeat > 10 then error(strErrorText(strError,strFileName,intRepeat)) end
			end
		end
	end -- function DeleteFile

	-- Make subfolder if does not exist --
	function fh.MakeFolder(strFolder,errFunction)
		if not fh.FlgFolderExists(strFolder) then
			local isOK, strError = lfs.mkdir(strFolder)
			if not isOK then
				local strMessage = "Cannot Make Folder: "..tostring(strError)..".\n"..strFolder.."\n"
				if type(errFunction) == "function" then
					errFunction(strMessage)
				else
					error(strMessage)
				end
				return false
			end
		end
		return true
	end -- function MakeFolder

	-- Invoke FH Shell Execute API --
	function fh.DoExecute(strExecutable,...)
		local arg = {...}
		local errFunction = fhMessageBox
		if type(arg[#arg]) == 'function' then
			errFunction = arg[#arg]
			table.remove(arg)
		end
		if fhGetAppVersion() > 5 and fhGetStringEncoding() == "UTF-8" then
			strExecutable = fhConvertANSItoUTF8(strExecutable)
		end
		local isOK, intErrorCode, strErrorText = fhShellExecute(strExecutable,unpack(arg))
		if not isOK then
			errFunction(tostring(strErrorText).." ("..tostring(intErrorCode)..")")
		end
		return isOK
	end -- function DoExecute

	-- Obtain the Day Number for any Date Point --									-- Fix problems with invalid dates in DayNumber function
	function fh.GetDayNumber(datDate)
		if datDate:IsNull() then return 0 end
		local intDay = fhCallBuiltInFunction("DayNumber",datDate) 				-- Only works for Gregorian dates that were not skipped nor BC dates
		if not intDay then
			local strError = "because "													-- Error message reason	-- V3.0
			local calendar = datDate:GetCalendar()
			local oldMonth = datDate:GetMonth()
			local oldDayNo = datDate:GetDay()
			local intMonth = math.min( oldMonth, 12 ) 								-- Limit month to 12, and day to last of each month
			local intDayNo = math.min( oldDayNo, ({0;31;28;31;30;31;30;31;31;30;31;30;31;})[intMonth+1] )
			local intYear  = datDate:GetYear()
			if oldDayNo > intDayNo then strError = strError.."day "..oldDayNo.." too big "   end
			if oldMonth > intMonth then strError = strError.."month "..oldMonth.." too big " end
			if calendar == "Hebrew" and intYear > 3761 then
				intYear = intYear - 3761
				strError = strError.."Hebrew year > 3761 "
			elseif calendar ~= "Gregorian" then
				strError = strError..calendar.." disallowed "
			end
			if     intYear == 1752 and intMonth ==  9 and intDayNo <= 13 then	-- Use 2 Sep 1752 for 3 - 13 Sep 1752 dates skipped
				intDayNo = 2
				strError = strError.."3 - 13 Sep 1752 skipped "
			elseif intYear == 1582 and intMonth == 10 and intDayNo <= 14 then	-- Use 4 Oct 1582 for 5 - 14 Oct 1582 dates skipped
				intDayNo = 4
				strError = strError.."5 - 14 Oct 1582 skipped "
			end	
			local setDate = fhNewDatePt(intYear,intMonth,intDayNo,datDate:GetYearDD())
			intDay = fhCallBuiltInFunction("DayNumber",setDate) 					-- Remove BC and Julian, Hebrew, French calendars
			if not intDay then intDay = 0 end
			local oldDate = fhNewDate()		oldDate:SetSimpleDate(datDate)		-- Report problem to user
			local newDate = fhNewDate()		newDate:SetSimpleDate(setDate)
			local strIsBC = ""
			if datDate:GetBC() then
				strError = strError.." B.C. disallowed "
				intDay = -intDay
				strIsBC = "and Day Number negated"
			end
			fhMessageBox("\n Get Day Number issue for date \n "..oldDate:GetDisplayText().." \n "..strError.." \n So replaced it with date \n "..newDate:GetDisplayText().." \n "..strIsBC)
		end
		return intDay
	end -- function GetDayNumber

	local dtpYearMin = fhNewDatePt(1000)												-- Minimum year to use when earliest estimate is null
	local dtpYearMax = fhNewDatePt(2000)												-- Maximum year to use when latest estimate is null

	function fh.GetYearToday()																-- Get the Year for Today
		local intYearToday = fhCallBuiltInFunction("Year",fhCallBuiltInFunction("Today"))
		dtpYearMax = fhNewDatePt(intYearToday)											-- Set maximum year date point
		return intYearToday
	end -- function GetYearToday()

	local function getDeathFacts(ptrIndi)												-- Iterate Death, Burial, Cremation facts
		local arrFact = { "~.DEAT"; "~.BURI"; "~.CREM"; }
		local intFact = 0
		local ptrFact = fhNewItemPtr()													-- Pointer to each fact returned to user
		return function ()
			while intFact < #arrFact do
				intFact = intFact + 1
				ptrFact = fhGetItemPtr(ptrIndi,arrFact[intFact])
				if ptrFact:IsNotNull() then return ptrFact end
			end
		end
	end -- local function getDeathFacts

	-- Ensure Estimated Date EARLIEST <= LATEST <= Fact Date --					-- Fix errors in EstimatedBirth/DeathDate function
	local function estimatedDates(strFunc,ptrIndi,intGens,getFact,intYrs)
		-- strFunc	"EstimatedBirthDate" or "EstimatedDeathDate"
		-- ptrIndi	Individual of interest
		-- intGens	Number of generations (may be nil)
		-- getFact	Iterator function for facts
		-- intYrs 	Years to add to After dates
		intGens = intGens or 2
		local dtpMin = fhCallBuiltInFunction(strFunc,ptrIndi,"EARLIEST",intGens)
		local dtpMax = fhCallBuiltInFunction(strFunc,ptrIndi,"LATEST",intGens)
		local dtpMid = fhNewDatePt()
		if not ( dtpMin:IsNull() and dtpMax:IsNull() ) then 						-- Skip if both null	
			if dtpMax:IsNull() then dtpMax = dtpYearMax elseif dtpMin:IsNull() then dtpMin = dtpYearMin end
			for ptrFact in getFact(ptrIndi) do
				local datFact = fhGetValueAsDate(fhGetItemPtr(ptrFact,"~.DATE"))
				if not datFact:IsNull() then												-- Find 1st Fact Date
					local dtpLast = datFact:GetDatePt1()								-- Last date = DatePt1 for Simple, Range, and Before
					local strType = datFact:GetSubtype()								-- Between = DatePt2 and After = DatePt1 + intYrs
					if   strType == "Between" then dtpLast = datFact:GetDatePt2()
					elseif strType == "After" then dtpLast = fhNewDatePt(dtpLast:GetYear()+intYrs,dtpLast:GetMonth(),dtpLast:GetDay()) end		-- Compare only uses Year, Month, Day so omitted	,dtpLast:GetYearDD(),dtpLast:GetBC(),dtpLast:GetCalendar()
					if dtpMax:Compare(dtpLast) > 0 then dtpMax = dtpLast end
					if dtpMin:Compare(dtpMax)  > 0 then dtpMin = dtpMax  end
					if strType ~= "After" then break end								-- Now EARLIEST <= LATEST <= Last date
				end
			end	
			local intDays = ( fh.GetDayNumber(dtpMax) - fh.GetDayNumber(dtpMin) ) / 2
			local intYear,remYear = math.modf( intDays / 365.2422 )				-- Offset year @ 365.2422 days per year, and remainder fraction
			local intMnth = math.floor( ( remYear * 12 ) + 0.1 )					-- Offset month is remainder fraction of year * 12
			dtpMid = fhCallBuiltInFunction("CalcDate",dtpMin,intYear,intMnth)	-- Need approximate MID year & month
		end
		return { Min=dtpMin; Mid=dtpMid; Max=dtpMax; }								-- Return EARLIEST, MID, LATEST dates
	end -- local function estimatedDates

	-- Make EstimatedBirthDate EARLIEST <= LATEST <= 1st Fact Date --			-- Fix errors in EstimatedBirthDate function
	function fh.EstimatedBirthDates(ptrIndi,intGens)
		return estimatedDates("EstimatedBirthDate",ptrIndi,intGens,iterate.Facts,10)
	end -- function EstimatedBirthDates

	-- Make EstimatedDeathDate EARLIEST <= LATEST <= DEAT/BURI/CREM Date --	-- Fix errors in EstimatedDeathDate function
	function fh.EstimatedDeathDates(ptrIndi,intGens)
		return estimatedDates("EstimatedDeathDate",ptrIndi,intGens,getDeathFacts,100)
	end -- function EstimatedDeathDates

	--[[
	@function:		BuildDataRef
	@description:	Get Full Data Reference for Pointer
	@parameters:		Item Pointer
	@returns:			Data Reference String, Record Id Integer, Record Type Tag String
	@requires:		None
	]]
	function fh.BuildDataRef(ptrRef)

		local strDataRef = ""										-- Data Reference with instance indices e.g. INDI.RESI[2].ADDR
		local intRecId   = 0										-- Record Id for associated Record
		local strRecTag  = ""										-- Record Tag of associated Record type i.e. INDI, FAM, NOTE, SOUR, etc

		-- getDataRef() is called recursively per level of the Data Ref
		-- ptrRef points to the upper Data Ref levels yet to be analysed
		-- strRef compiles the lower Data Ref levels including instances

		local function getDataRef(ptrRef,strRef)
			local ptrTag = ptrRef:Clone()
			local strTag = fhGetTag(ptrTag)						-- Current level Tag
			ptrTag:MoveToParentItem(ptrTag)
			if ptrTag:IsNotNull() then							-- Parent level exists
				local intSib = 1
				local ptrSib = ptrRef:Clone()					-- Pointer to siblings with same Tag
				ptrSib:MovePrev("SAME_TAG")
				while ptrSib:IsNotNull() do						-- Count previous siblings with same Tag
					intSib = intSib + 1
					ptrSib:MovePrev("SAME_TAG")
				end
				if intSib > 1 then 	strTag = strTag.."["..intSib.."]" end
				getDataRef(ptrTag,"."..strTag..strRef)			-- Now analyse the parent level
			else
				strDataRef = strTag..strRef						-- Record level reached, so set return values
				intRecId   = fhGetRecordId(ptrRef)
				strRecTag  = strTag
				if not fhIsValidDataRef(strDataRef) then print("BuildDataRef: "..strDataRef.." is Invalid") end
			end
		end -- local function getDataRef

		if type(ptrRef) == "userdata" then getDataRef(ptrRef,"") end

		return strDataRef, intRecId, strRecTag

	end -- function BuildDataRef

	--[[
	@function:		GetDataRefPtr
	@description:	Get Pointer for Full Data Reference
	@parameters:		Data Reference String, Record Id Integer, Record Type Tag String (optional)
	@returns:			Item Pointer which IsNull() if any parameters are invalid
	@requires:		None
	]]
	function fh.GetDataRefPtr(strDataRef,intRecId,strRecTag)
		strDataRef = strDataRef or ""
		if not strRecTag then
			strRecTag = strDataRef:gsub("^(%u+).*$","%1")	-- Extract Record Tag from Data Ref
		end
		local ptrRef = fhNewItemPtr()
		ptrRef:MoveToRecordById(strRecTag,intRecId or 0)	-- Lookup the Record by Id
		ptrRef:MoveTo(ptrRef,strDataRef)						-- Move to the Data Ref
		return ptrRef
	end -- function GetDataRefPtr

	function fh.TblDataRef(ptrRef)
		local tblRef = {}
		tblRef.DataRef, tblRef.RecId, tblRef.RecTag = BuildDataRef(ptrRef)
		return tblRef
	end -- function TblDataRef

	function fh.PtrDataRef(tblRef)
		local tblRef = tblRef or {}								-- Ensure table and its fields exist
		return GetDataRefPtr(tblRef.DataRef or "",tblRef.RecId or 0,tblRef.RecTag or "")
	end -- function PtrDataRef

	return fh

end -- local function general_v3

local general = general_v3()										-- To access FH general tools module

--[[
@Module:			+fh+encoder_v3
@Author:			Mike Tate
@Version:			3.5
@LastUpdated:	25 Aug 2020
@Description:	Text encoder module for HTML XHTML XML URI UTF8 UTF16 ISO CP1252/ANSI character codings.
@V3.5:				Function Prototype Closure version with Lua 5.1 & 5.3 comaptibility.
@V3.4:				Ensure expressions involving gsub return just text parameter.
@V3.3:				Adds UNICODE U+10000 to U+10FFFF UTF-16 Supplementary Planes.
@V3.2:				Update for ANSI & Unicode to ASCII for sorting, Soundex, etc.
@V3.1:				Update for Unicode UTF-16 & UTF-8 and fhConvertANSItoUTF8 & fhConvertUTF8toANSI, name change UTF to UTF8 & CP to ANSI.
@V2.0:				StrUTF8_Encode() replaced by StrUTF_CP1252() for entire UTF-8 range, plus new StrCP1252_ISO().
@V1.0:				Initial version.
]]

local function encoder_v3()

	local fh = {}													-- Local environment table

	local fhVersion = fhGetAppVersion()

	local br_Tag = "
" -- Markup language break tag default local br_Lua = "
" -- Lua pattern for break tag recognition local tblCodePage = {} -- Code Page to XML/XHTML/HTML/URI/UTF8 encodings: http://en.wikipedia.org/wiki/Windows-1252 & 1250 & etc -- Control characters "\000" to "\031" for URI & Markup "[%c]" encodings are disallowed except for "\t" to "\r" tblCodePage["\000"] = "" -- NUL tblCodePage["\001"] = "" -- SOH tblCodePage["\002"] = "" -- STX tblCodePage["\003"] = "" -- ETX tblCodePage["\004"] = "" -- EOT tblCodePage["\005"] = "" -- ENQ tblCodePage["\006"] = "" -- ACK tblCodePage["\a"] = "" -- BEL tblCodePage["\b"] = "" -- BS tblCodePage["\t"] = "+" -- HT space in Markup see setURIEncodings() and setMarkupEncodings() below tblCodePage["\n"] = "%0A" -- LF br_Tag in Markup tblCodePage["\v"] = "%0A" -- VT br_Tag in Markup tblCodePage["\f"] = "%0A" -- FF br_Tag in Markup tblCodePage["\r"] = "%0D" -- CR br_Tag in Markup tblCodePage["\014"] = "" -- SO tblCodePage["\015"] = "" -- SI tblCodePage["\016"] = "" -- DLE tblCodePage["\017"] = "" -- DC1 tblCodePage["\018"] = "" -- DC2 tblCodePage["\019"] = "" -- DC3 tblCodePage["\020"] = "" -- DC4 tblCodePage["\021"] = "" -- NAK tblCodePage["\022"] = "" -- SYN tblCodePage["\023"] = "" -- ETB tblCodePage["\024"] = "" -- CAN tblCodePage["\025"] = "" -- EM tblCodePage["\026"] = "" -- SUB tblCodePage["\027"] = "" -- ESC tblCodePage["\028"] = "" -- FS tblCodePage["\029"] = "" -- GS tblCodePage["\030"] = "" -- RS tblCodePage["\031"] = "" -- US -- ASCII characters "\032" to "\127" for URI "[%s%p]" encodings: http://en.wikipedia.org/wiki/URL and http://en.wikipedia.org/wiki/Percent-encoding tblCodePage[" "] = "+" -- or "%20" Space tblCodePage["!"] = "%21" -- Reserved character tblCodePage['"'] = "%22" -- """ in Markup see setURIEncodings() and setMarkupEncodings() below tblCodePage["#"] = "%23" -- Reserved character tblCodePage["$"] = "%24" -- Reserved character tblCodePage["%"] = "%25" -- Must be encoded tblCodePage["&"] = "%26" -- Reserved character -- "&" in Markup see setURIEncodings() and setMarkupEncodings() below tblCodePage["'"] = "%27" -- Reserved character -- "'" in Markup see setURIEncodings() and setMarkupEncodings() below tblCodePage["("] = "%28" -- Reserved character tblCodePage[")"] = "%29" -- Reserved character tblCodePage["*"] = "%2A" -- Reserved character tblCodePage["+"] = "%2B" -- Reserved character tblCodePage[","] = "%2C" -- Reserved character -- tblCodePage["-"] = "%2D" -- Unreserved character not encoded -- tblCodePage["."] = "%2E" -- Unreserved character not encoded tblCodePage["/"] = "%2F" -- Reserved character -- Digits 0 to 9 -- Unreserved characters not encoded tblCodePage[":"] = "%3A" -- Reserved character tblCodePage[";"] = "%3B" -- Reserved character tblCodePage["<"] = "%3C" -- "<" in Markup see setURIEncodings() and setMarkupEncodings() below tblCodePage["="] = "%3D" -- Reserved character tblCodePage[">"] = "%3E" -- ">" in Markup see setURIEncodings() and setMarkupEncodings() below tblCodePage["?"] = "%3F" -- Reserved character tblCodePage["@"] = "%40" -- Reserved character -- Letters A to Z -- Unreserved characters not encoded tblCodePage["["] = "%5B" -- Reserved character tblCodePage["\\"]= "%5C" tblCodePage["]"] = "%5D" -- Reserved character tblCodePage["^"] = "%5E" -- tblCodePage["_"] = "%5F" -- Unreserved character not encoded tblCodePage["`"] = "%60" -- Letters a to z -- Unreserved characters not encoded tblCodePage["{"] = "%7B" tblCodePage["|"] = "%7C" tblCodePage["}"] = "%7D" -- tblCodePage["~"] = "%7E" -- Unreserved character not encoded tblCodePage["\127"] = "" -- DEL -- Code Page 1252 Unicode characters "\128" to "\255" for UTF-8 scheme "[-]" encodings: http://en.wikipedia.org/wiki/UTF-8 tblCodePage[""] = string.char(0xE2,0x82,0xAC) -- "€" tblCodePage["\129"] = "" -- Undefined tblCodePage[""] = string.char(0xE2,0x80,0x9A) tblCodePage[""] = string.char(0xC6,0x92) tblCodePage[""] = string.char(0xE2,0x80,0x9E) tblCodePage[""] = string.char(0xE2,0x80,0xA6) tblCodePage[""] = string.char(0xE2,0x80,0xA0) tblCodePage[""] = string.char(0xE2,0x80,0xA1) tblCodePage[""] = string.char(0xCB,0x86) tblCodePage[""] = string.char(0xE2,0x80,0xB0) tblCodePage[""] = string.char(0xC5,0xA0) tblCodePage[""] = string.char(0xE2,0x80,0xB9) tblCodePage[""] = string.char(0xC5,0x92) tblCodePage["\141"] = "" -- Undefined tblCodePage[""] = string.char(0xC5,0xBD) tblCodePage["\143"] = "" -- Undefined tblCodePage["\144"] = "" -- Undefined tblCodePage[""] = string.char(0xE2,0x80,0x98) tblCodePage[""] = string.char(0xE2,0x80,0x99) tblCodePage[""] = string.char(0xE2,0x80,0x9C) tblCodePage[""] = string.char(0xE2,0x80,0x9D) tblCodePage[""] = string.char(0xE2,0x80,0xA2) tblCodePage[""] = string.char(0xE2,0x80,0x93) tblCodePage[""] = string.char(0xE2,0x80,0x94) tblCodePage["\152"] = string.char(0xCB,0x9C) -- Small Tilde tblCodePage[""] = string.char(0xE2,0x84,0xA2) tblCodePage[""] = string.char(0xC5,0xA1) tblCodePage[""] = string.char(0xE2,0x80,0xBA) tblCodePage[""] = string.char(0xC5,0x93) tblCodePage["\157"] = "" -- Undefined tblCodePage[""] = string.char(0xC5,0xBE) tblCodePage[""] = string.char(0xC5,0xB8) tblCodePage["\160"] = string.char(0xC2,0xA0) -- " " No Break Space tblCodePage[""] = string.char(0xC2,0xA1) -- "¡" tblCodePage[""] = string.char(0xC2,0xA2) -- "¢" tblCodePage[""] = string.char(0xC2,0xA3) -- "£" tblCodePage[""] = string.char(0xC2,0xA4) -- "¤" tblCodePage[""] = string.char(0xC2,0xA5) -- "¥" tblCodePage[""] = string.char(0xC2,0xA6) tblCodePage[""] = string.char(0xC2,0xA7) tblCodePage[""] = string.char(0xC2,0xA8) tblCodePage[""] = string.char(0xC2,0xA9) tblCodePage[""] = string.char(0xC2,0xAA) tblCodePage[""] = string.char(0xC2,0xAB) tblCodePage[""] = string.char(0xC2,0xAC) tblCodePage[""] = string.char(0xC2,0xAD) -- "­" Soft Hyphen tblCodePage[""] = string.char(0xC2,0xAE) tblCodePage[""] = string.char(0xC2,0xAF) tblCodePage[""] = string.char(0xC2,0xB0) tblCodePage[""] = string.char(0xC2,0xB1) tblCodePage[""] = string.char(0xC2,0xB2) tblCodePage[""] = string.char(0xC2,0xB3) tblCodePage[""] = string.char(0xC2,0xB4) tblCodePage[""] = string.char(0xC2,0xB5) tblCodePage[""] = string.char(0xC2,0xB6) tblCodePage[""] = string.char(0xC2,0xB7) tblCodePage[""] = string.char(0xC2,0xB8) tblCodePage[""] = string.char(0xC2,0xB9) tblCodePage[""] = string.char(0xC2,0xBA) tblCodePage[""] = string.char(0xC2,0xBB) tblCodePage[""] = string.char(0xC2,0xBC) tblCodePage[""] = string.char(0xC2,0xBD) tblCodePage[""] = string.char(0xC2,0xBE) tblCodePage[""] = string.char(0xC2,0xBF) tblCodePage[""] = string.char(0xC3,0x80) tblCodePage[""] = string.char(0xC3,0x81) tblCodePage[""] = string.char(0xC3,0x82) tblCodePage[""] = string.char(0xC3,0x83) tblCodePage[""] = string.char(0xC3,0x84) tblCodePage[""] = string.char(0xC3,0x85) tblCodePage[""] = string.char(0xC3,0x86) tblCodePage[""] = string.char(0xC3,0x87) tblCodePage[""] = string.char(0xC3,0x88) tblCodePage[""] = string.char(0xC3,0x89) tblCodePage[""] = string.char(0xC3,0x8A) tblCodePage[""] = string.char(0xC3,0x8B) tblCodePage[""] = string.char(0xC3,0x8C) tblCodePage[""] = string.char(0xC3,0x8D) tblCodePage[""] = string.char(0xC3,0x8E) tblCodePage[""] = string.char(0xC3,0x8F) tblCodePage[""] = string.char(0xC3,0x90) tblCodePage[""] = string.char(0xC3,0x91) tblCodePage[""] = string.char(0xC3,0x92) tblCodePage[""] = string.char(0xC3,0x93) tblCodePage[""] = string.char(0xC3,0x94) tblCodePage[""] = string.char(0xC3,0x95) tblCodePage[""] = string.char(0xC3,0x96) tblCodePage[""] = string.char(0xC3,0x97) tblCodePage[""] = string.char(0xC3,0x98) tblCodePage[""] = string.char(0xC3,0x99) tblCodePage[""] = string.char(0xC3,0x9A) tblCodePage[""] = string.char(0xC3,0x9B) tblCodePage[""] = string.char(0xC3,0x9C) tblCodePage[""] = string.char(0xC3,0x9D) tblCodePage[""] = string.char(0xC3,0x9E) tblCodePage[""] = string.char(0xC3,0x9F) tblCodePage[""] = string.char(0xC3,0xA0) tblCodePage[""] = string.char(0xC3,0xA1) tblCodePage[""] = string.char(0xC3,0xA2) tblCodePage[""] = string.char(0xC3,0xA3) tblCodePage[""] = string.char(0xC3,0xA4) tblCodePage[""] = string.char(0xC3,0xA5) tblCodePage[""] = string.char(0xC3,0xA6) tblCodePage[""] = string.char(0xC3,0xA7) tblCodePage[""] = string.char(0xC3,0xA8) tblCodePage[""] = string.char(0xC3,0xA9) tblCodePage[""] = string.char(0xC3,0xAA) tblCodePage[""] = string.char(0xC3,0xAB) tblCodePage[""] = string.char(0xC3,0xAC) tblCodePage[""] = string.char(0xC3,0xAD) tblCodePage[""] = string.char(0xC3,0xAE) tblCodePage[""] = string.char(0xC3,0xAF) tblCodePage[""] = string.char(0xC3,0xB0) tblCodePage[""] = string.char(0xC3,0xB1) tblCodePage[""] = string.char(0xC3,0xB2) tblCodePage[""] = string.char(0xC3,0xB3) tblCodePage[""] = string.char(0xC3,0xB4) tblCodePage[""] = string.char(0xC3,0xB5) tblCodePage[""] = string.char(0xC3,0xB6) tblCodePage[""] = string.char(0xC3,0xB7) tblCodePage[""] = string.char(0xC3,0xB8) tblCodePage[""] = string.char(0xC3,0xB9) tblCodePage[""] = string.char(0xC3,0xBA) tblCodePage[""] = string.char(0xC3,0xBB) tblCodePage[""] = string.char(0xC3,0xBC) tblCodePage[""] = string.char(0xC3,0xBD) tblCodePage[""] = string.char(0xC3,0xBE) tblCodePage[""] = string.char(0xC3,0xBF) -- Set XML/XHTML/HTML "[%c\"&'<>]" Markup encodings: http://en.wikipedia.org/wiki/XML and http://en.wikipedia.org/wiki/HTML local function setMarkupEncodings() tblCodePage["\t"] = " " -- HT "\t" to "\r" are treated as white space in Markup Languages by default tblCodePage["\n"] = br_Tag -- LF tblCodePage["\v"] = br_Tag -- VT line break tag "
" or "
" or "
" or "
" is better tblCodePage["\f"] = br_Tag -- FF tblCodePage["\r"] = br_Tag -- CR tblCodePage['"'] = """ tblCodePage["&"] = "&" tblCodePage["'"] = "'" tblCodePage["<"] = "<" tblCodePage[">"] = ">" end -- local function setMarkupEncodings -- Set URI/URL/URN "[%s%p]" encodings: http://en.wikipedia.org/wiki/URL and http://en.wikipedia.org/wiki/Percent-encoding local function setURIEncodings() tblCodePage["\t"] = "+" -- HT space tblCodePage["\n"] = "%0A" -- LF newline tblCodePage["\v"] = "%0A" -- VT newline tblCodePage["\f"] = "%0A" -- FF newline tblCodePage["\r"] = "%0D" -- CR return tblCodePage['"'] = "%22" tblCodePage["&"] = "%26" tblCodePage["'"] = "%27" tblCodePage["<"] = "%3C" tblCodePage[">"] = "%3E" end -- local function setURIEncodings -- Encode characters according to gsub pattern & lookup table -- local function strEncode(strText,strPattern,tblPattern) return ( (strText or ""):gsub(strPattern,tblPattern) ) -- V3.4 end -- local function strEncode -- Encode CP1252/ANSI characters into UTF-8 codes -- function fh.StrANSI_UTF8(strText) if fhVersion > 5 then strText = fhConvertANSItoUTF8(strText) else strText = strEncode(strText,"[\127-]",tblCodePage) end return strText end -- function StrANSI_UTF8 function fh.StrCP_UTF(strText) -- Legacy return fh.StrANSI_UTF8(strText) end -- function StrCP1252_UTF8 function fh.StrCP1252_UTF(strText) -- Legacy return fh.StrANSI_UTF8(strText) end -- function StrCP1252_UTF -- Encode CP1252/ANSI or UTF-8 characters into UTF-8 -- function fh.StrEncode_UTF8(strText) if stringx.encoding() == "ANSI" then return fh.StrANSI_UTF8(strText) else return strText end end -- function StrEncode_UTF8 -- Encode CP1252/ANSI characters into XML/XHTML/HTML/UTF8 codes -- local strANSI_XML = "[%z\001-\031\"&'<>\127-]" if fhVersion > 6 then -- Cater for Lua 5.1 %z or Lua 5.3 \0 strANSI_XML = "[\000-\031\"&'<>\127-]" end function fh.StrANSI_XML(strText) setMarkupEncodings() strText = (strText or ""):gsub(br_Lua,"\n") -- Convert
&
&
&
to \n that becomes br_Tag strText = strEncode(strText,strANSI_XML,tblCodePage) return strText end -- function StrANSI_XML function StrCP_XML(strText) -- Legacy return fh.StrANSI_XML(strText) end -- function StrCP_XML function StrCP1252_XML(strText) -- Legacy return fh.StrANSI_XML(strText) end -- function StrCP1252_XML -- Encode UTF-8 ASCII characters into XML/XHTML/HTML codes -- local strUTF8_XML = "[%z\001-\031\"&'<>\127]" if fhVersion > 6 then -- Cater for Lua 5.1 %z or Lua 5.3 \0 strUTF8_XML = "[\000-\031\"&'<>\127]" end function fh.StrUTF8_XML(strText) setMarkupEncodings() strText = (strText or ""):gsub(br_Lua,"\n") -- Convert
&
&
&
to \n that becomes br_Tag strText = strEncode(strText,strUTF8_XML,tblCodePage) return strText end -- function StrUTF8_XML -- Encode CP1252/ANSI or UTF-8 ASCII characters into XML/XHTML/HTML codes -- function fh.StrEncode_XML(strText) if stringx.encoding() == "ANSI" then return fh.StrANSI_XML(strText) else return fh.StrUTF8_XML(strText) end end -- function StrEncode_XML -- Encode Item Text characters into XML/HTML/UTF-8 codes -- function fh.StrGetItem_XML(ptrItem,strTags) return fh.StrEncode_XML(fhGetItemText(ptrItem,strTags)) end -- function StrGetItem_XML -- Encode CP1252/ANSI characters into URI codes -- function fh.StrANSI_URI(strText) setURIEncodings() strText = (strText or ""):gsub(br_Lua,"\n") -- Convert
&
&
&
to \n that becomes %0A strText = strEncode(strText,"[^0-9A-Za-z]",tblCodePage) return strText end -- function StrANSI_URI function fh.StrCP_URI(strText) return fh.StrANSI_URI(strText) end -- function StrCP_URI function fh.StrCP1252_URI(strText) return fh.StrANSI_URI(strText) end -- function StrCP1252_URI -- Encode UTF-8 ASCII characters into URI codes -- local strUTF8_URI = "[%z\001-\127]" if fhVersion > 6 then -- Cater for Lua 5.1 %z or Lua 5.3 \0 strUTF8_URI = "[\000-\127]" end function fh.StrUTF8_URI(strText) setURIEncodings() strText = (strText or ""):gsub(br_Lua,"\n") -- Convert
&
&
&
to \n that becomes br_Tag strText = strEncode(strText,strUTF8_URI,tblCodePage) return strText end -- function StrUTF8_URI -- Encode CP1252/ANSI or UTF-8 ASCII characters into URI codes -- function fh.StrEncode_URI(strText) if stringx.encoding() == "ANSI" then return fh.StrANSI_URI(strText) else return fh.StrUTF8_URI(strText) end end -- function StrEncode_URI function fh.StrUTF8_Encode(strText) -- Legacy from V1.0 return fh.StrUTF8_ANSI(strText) end -- function StrUTF8_Encode -- Encode UTF-8 bytes into single CP1252/ANSI character V2.0 upvalues -- local strByteRange = "["..string.char(0xC0).."-"..string.char(0xFF).."]" local tblBytePoint = {0xC0;0xE0;0xF0;0xF8;0xFC;} -- Byte codes for 2-byte, 3-byte, 4-byte, 5-byte, 6-byte UTF-8 local tblUTF8 = {} for strByte = string.byte(""), string.byte("") do local strChar = string.char(strByte) -- Use CodePage to UTF-8 table to populate UTF-8 to CodePage table local strCode = tblCodePage[strChar] tblUTF8[strCode] = strChar end -- Encode UTF-8 bytes into single CP1252/ANSI character -- function fh.StrUTF8_ANSI(strText) strText = strText or "" if fhVersion > 5 then return fhConvertUTF8toANSI(strText) end if strText:match(strByteRange) then -- If text contains characters that need translating then local intChar = 0 -- Input character index local strChar = "" -- Current character local strCode = "" -- UTF-8 multi-byte code local tblLine = {} -- Translated output line repeat intChar = intChar + 1 -- Step through each character in text strChar = strText:sub(intChar,intChar) if strChar:match(strByteRange) then -- Convert UTF-8 bytes into CP character strCode = strChar -- First UTF-8 byte code, whose top bits say how many bytes to append for intByte, strByte in ipairs(tblBytePoint) do if string.byte(strChar) >= strByte then intChar = intChar + 1 -- Append next UTF-8 byte code character strCode = strCode..strText:sub(intChar,intChar) else break end end strChar = tblUTF8[strCode] or "" -- Translate UTF-8 code into CP character end table.insert(tblLine,strChar) -- Accumulate output char by char until intChar >= #strText strText = table.concat(tblLine) end return strText end -- function StrUTF8_ANSI function fh.StrUTF_CP(strText) -- Legacy return fh.StrUTF8_ANSI(strText) end -- function StrUTF_CP function fh.StrUTF_CP1252(strText) -- Legacy return fh.StrUTF8_ANSI(strText) end -- function StrUTF_CP1252 -- Encode CP1252/ANSI or UTF-8 characters into ANSI -- function fh.StrEncode_ANSI(strText) if stringx.encoding() == "ANSI" then return strText or "" else return fh.StrUTF8_ANSI(strText) end end -- function StrEncode_ANSI -- Set ISO-8859-1 "[\127-]" encodings: http://en.wikipedia.org/wiki/ISO/IEC_8859-1 local tblISO8859 = { } tblISO8859["\127"]="" -- DEL tblISO8859[""] = "EUR" tblISO8859["\129"]="" -- Undefined tblISO8859[""] = "" tblISO8859[""] = "f" tblISO8859[""] = "" tblISO8859[""] = "..." tblISO8859[""] = "+" tblISO8859[""] = "" tblISO8859[""] = "^" tblISO8859[""] = "%" tblISO8859[""] = "S" tblISO8859[""] = "<" tblISO8859[""] = "OE" tblISO8859["\141"]="" -- Undefined tblISO8859[""] = "Z" tblISO8859["\143"]="" -- Undefined tblISO8859["\144"]="" -- Undefined tblISO8859[""] = "'" tblISO8859[""] = "'" tblISO8859[""] = '"' tblISO8859[""] = '"' tblISO8859[""] = "" tblISO8859[""] = "-" tblISO8859[""] = "-" tblISO8859["\152"]="~" -- Small Tilde tblISO8859[""] = "TM" tblISO8859[""] = "s" tblISO8859[""] = ">" tblISO8859[""] = "oe" tblISO8859["\157"]="" -- Undefined tblISO8859[""] = "z" tblISO8859[""] = "Y" -- Encode CP1252/ANSI characters into ISO-8859-1 codes -- function fh.StrANSI_ISO(strText) return strEncode(strText,"[\127-]",tblISO8859) end -- function StrANSI_ISO function fh.StrCP_ISO(strText) -- Legacy return fh.StrANSI_ISO(strText) end -- function StrCP_ISO function fh.StrCP1252_ISO(strText) -- Legacy return fh.StrANSI_ISO(strText) end -- function StrCP1252_ISO function fh.StrUTF8_ISO(strText) return fh.StrANSI_ISO(fh.StrUTF8_ANSI(strText)) end -- function StrUTF8_ISO -- Encode CP1252/ANSI or UTF-8 ASCII characters into ISO-8859-1 codes -- function fh.StrEncode_ISO(strText) if stringx.encoding() == "ANSI" then return fh.StrANSI_ISO(strText) else return fh.StrUTF8_ISO(strText) end end -- function StrEncode_ISO -- Convert UTF-8 bytes to a UTF-16 word or pair -- local tblByte = {} local tblLead = { 0x80; 0xC0; 0xE0; 0xF0; 0xF8; 0xFC; } function fh.StrUtf8toUtf16(strChar) -- Convert any UTF-8 multibytes to UTF-16 -- local function strUtf8() if #tblByte > 0 then local intUtf16 = 0 for intIndex, intByte in ipairs (tblByte) do -- Convert UTF-8 bytes to UNICODE U+0080 to U+10FFFF if intIndex == 1 then intUtf16 = intByte - tblLead[#tblByte] else intUtf16 = intUtf16 * 0x40 + intByte - 0x80 end end if intUtf16 > 0xFFFF then -- U+10000 to U+10FFFF Supplementary Planes -- V2.6 tblByte = {} intUtf16 = intUtf16 - 0x10000 local intLow10 = 0xDC00 + ( intUtf16 % 0x400 ) -- Low 16-bit Surrogate local intTop10 = 0xD800 + math.floor( intUtf16 / 0x400 ) -- High 16-bit Surrogate local intChar1 = intTop10 % 0x100 local intChar2 = math.floor( intTop10 / 0x100 ) local intChar3 = intLow10 % 0x100 local intChar4 = math.floor( intLow10 / 0x100 ) return string.char(intChar1,intChar2,intChar3,intChar4) -- Surrogate 16-bit Pair end if intUtf16 < 0xD800 -- U+0080 to U+FFFF (except U+D800 to U+DFFF) -- V2.6 or intUtf16 > 0xDFFF then -- Basic Multilingual Plane tblByte = {} local intChar1 = intUtf16 % 0x100 local intChar2 = math.floor( intUtf16 / 0x100 ) return string.char(intChar1,intChar2) -- BPL 16-bit end local strUtf8 = "" -- U+D800 to U+DFFF Reserved Code Points -- V2.6 for intIndex, intByte in ipairs (tblByte) do strUtf8 = strUtf8..string.format("%.2X ",intByte) end local strUtf16 = string.format("%.4X ",intUtf16) fhMessageBox("\n UTF-16 Reserved Code Point U+D800 to U+DFFF \n UTF-16 = "..strUtf16.." UTF-8 = "..strUtf8.."\n Character will be replaced by a question mark. \n") tblByte = {} return "?\0" end return "" end -- local function strUtf8 local intUtf8 = string.byte(strChar) if intUtf8 < 0x80 then -- U+0000 to U+007F (ASCII) return strUtf8()..strChar.."\0" -- Previous UTF-8 multibytes + current ASCII char end if intUtf8 >= 0xC0 then -- Next UTF-8 multibyte start local strUtf16 = strUtf8() table.insert(tblByte,intUtf8) return strUtf16 -- Previous UTF-8 multibytes end table.insert(tblByte,intUtf8) return "" end -- function StrUtf8toUtf16 -- Encode UTF-8 bytes into UTF-16 words -- function fh.StrUTF8_UTF16(strText) tblByte = {} -- (0xFF) flushes last UTF-8 character return ( ((strText or "")..string.char(0xFF)):gsub("(.)",fh.StrUtf8toUtf16) ) -- V3.4 end -- function StrUTF8_UTF16 -- Encode CP1252/ANSI or UTF-8 characters into UTF-16 words -- function fh.StrEncode_UTF16(strText) if stringx.encoding() == "ANSI" then strText = fh.StrANSI_UTF8(strText) end return fh.StrUTF8_UTF16(strText) end -- function StrEncode_UTF16 local intTop10 = 0 -- Convert a UTF-16 word or pair to UTF-8 bytes -- function fh.StrUtf16toUtf8(strChar1,strChar2) local intUtf16 = string.byte(strChar2) * 0x100 + string.byte(strChar1) if intUtf16 < 0x80 then -- U+0000 to U+007F (ASCII) return string.char(intUtf16) end if intUtf16 < 0x800 then -- U+0080 to U+07FF local intByte1 = intUtf16 % 0x40 intUtf16 = math.floor( intUtf16 / 0x40 ) local intByte2 = intUtf16 return string.char( intByte2 + 0xC0, intByte1 + 0x80 ) end if intUtf16 < 0xD800 -- U+0800 to U+FFFF or intUtf16 > 0xDFFF then local intByte1 = intUtf16 % 0x40 intUtf16 = math.floor( intUtf16 / 0x40 ) local intByte2 = intUtf16 % 0x40 intUtf16 = math.floor( intUtf16 / 0x40 ) local intByte3 = intUtf16 return string.char( intByte3 + 0xE0, intByte2 + 0x80, intByte1 + 0x80 ) end if intUtf16 < 0xDC00 then -- U+10000 to U+10FFFF High 16-bit Surrogate Supplementary Planes -- V2.6 intTop10 = ( intUtf16 - 0xD800 ) * 0x400 + 0x10000 return "" end intUtf16 = intUtf16 - 0xDC00 + intTop10 -- U+10000 to U+10FFFF Low 16-bit Surrogate Supplementary Planes -- V2.6 local intByte1 = intUtf16 % 0x40 intUtf16 = math.floor( intUtf16 / 0x40 ) local intByte2 = intUtf16 % 0x40 intUtf16 = math.floor( intUtf16 / 0x40 ) local intByte3 = intUtf16 % 0x40 intUtf16 = math.floor( intUtf16 / 0x40 ) local intByte4 = intUtf16 return string.char( intByte4 + 0xF0, intByte3 + 0x80, intByte2 + 0x80, intByte1 + 0x80 ) end -- function StrUtf16toUtf8 -- Encode UTF-16 words into UTF-8 bytes -- function fh.StrUTF16_UTF8(strText) return ( (strText or ""):gsub("(.)(.)",fh.StrUtf16toUtf8) ) -- V3.4 end -- function StrUTF16_UTF8 -- Encode UTF-16 words into ANSI characters -- function fh.StrUTF16_ANSI(strText) return fh.StrUTF8_ANSI(fh.StrUTF16_UTF8(strText)) end -- function StrUTF16_ANSI -- Read UTF-16/UTF-8/ANSI file converted to chosen encoding via line iterator -- local strUtf16 = "^.%z" if fhVersion > 6 then -- Cater for Lua 5.1 %z or Lua 5.3 \0 strUtf16 = "^.\0" end function fh.FileLines(strFileName,strEncoding) -- Derived from http://lua-users.org/wiki/EnhancedFileLines local bomUtf16= "^"..string.char(0xFF,0xFE) -- "" local bomUtf8 = "^"..string.char(0xEF,0xBB,0xBF) -- "" local fncConv = tostring -- Function to convert input to current encoding local intHead = 1 -- Index to start of current text line local intLump = 1024 local fHandle = general.OpenFile(strFileName,"rb") local strText = fHandle:read(1024) -- Read first lump from file local intBOM = 0 strEncoding = strEncoding or string.encoding() if strText:match(bomUtf16) or strText:match(strUtf16) then strText,intBOM = strText:gsub(bomUtf16,"") -- Strip UTF-16 BOM if strEncoding == "ANSI" then -- Define UTF-16 conversion to current encoding fncConv = fh.StrUTF16_ANSI else fncConv = fh.StrUTF16_UTF8 end elseif strText:match(bomUtf8) then strText,intBOM = strText:gsub(bomUtf8,"") -- Strip UTF-8 BOM if strEncoding == "ANSI" then -- Define UTF-8 conversion to current encoding fncConv = fh.StrUTF8_ANSI end else if strEncoding == "UTF-8" then -- Define ANSI conversion to current encoding fncConv = fh.StrANSI_UTF8 end end strText = fncConv(strText) -- Convert first lump of text return function() -- Iterator function local intTail,strTail -- Index to end of current text line, and terminating characters while true do intTail, strTail = strText:match("()([\r\n].)",intHead) if intTail or not fHandle then if intHead > 1 then intLump = 0 end break -- End of line or end of file elseif fHandle then local strLump = fHandle:read(1024) -- Read next lump from file if strLump then -- Strip old text and add converted lump strText = strText:sub(intHead)..fncConv(strLump) intHead = 1 intLump = 1024 else assert(fHandle:close()) -- End of file fHandle = nil end end end if not intTail then intTail = #strText -- Last fragment of file elseif strTail == "\r\n" then intTail = intTail + 1 -- Adjust tail for both \r & \n end local strLine = strText:sub(intHead,intTail) -- Extract line from text intHead = intTail + 1 if #strLine > 0 then -- Return pruned line, tail chars, lump bytes read local strBody, strTail = strLine:match("^(.-)([\r\n]+)$") return strBody, strTail, intLump end end end -- function FileLines -- Set "[-]" ASCII encodings same as Unidecode below local tblASCII = { } tblASCII[""] = "=E" tblASCII["\129"]="" -- Undefined tblASCII[""] = "," tblASCII[""] = "f" tblASCII[""] = ",," tblASCII[""] = "..." tblASCII[""] = "|+" tblASCII[""] = "|++" tblASCII[""] = "^" tblASCII[""] = "%0" tblASCII[""] = "S" tblASCII[""] = "<" tblASCII[""] = "OE" tblASCII["\141"]="" -- Undefined tblASCII[""] = "Z" tblASCII["\143"]="" -- Undefined tblASCII["\144"]="" -- Undefined tblASCII[""] = "'" tblASCII[""] = "'" tblASCII[""] = "\"" tblASCII[""] = "\"" tblASCII[""] = "*" tblASCII[""] = "-" tblASCII[""] = "--" tblASCII["\152"]="~" -- Small Tilde tblASCII[""] = "TM" tblASCII[""] = "s" tblASCII[""] = ">" tblASCII[""] = "oe" tblASCII["\157"]="" -- Undefined tblASCII[""] = "z" tblASCII[""] = "Y" tblASCII["\160"]=" " -- " " No Break Space tblASCII[""] = "!" -- "¡" tblASCII[""] = "=c" -- "¢" tblASCII[""] = "=L" -- "£" tblASCII[""] = "=$" -- "¤" tblASCII[""] = "=Y" -- "¥" tblASCII[""] = "|" tblASCII[""] = "=SS" tblASCII[""] = "\"" tblASCII[""] = "(C)" tblASCII[""] = "a" tblASCII[""] = "<<" tblASCII[""] = "-" tblASCII[""] = "-" -- "­" Soft Hyphen tblASCII[""] = "(R)" tblASCII[""] = "-" tblASCII[""] = "=o" tblASCII[""] = "+-" tblASCII[""] = "2" tblASCII[""] = "3" tblASCII[""] = "'" tblASCII[""] = "=u" tblASCII[""] = "=p" tblASCII[""] = "*" tblASCII[""] = "," tblASCII[""] = "1" tblASCII[""] = "o" tblASCII[""] = ">>" tblASCII[""] = "1/4" tblASCII[""] = "1/2" tblASCII[""] = "3/4" tblASCII[""] = "?" tblASCII[""] = "A" tblASCII[""] = "A" tblASCII[""] = "A" tblASCII[""] = "A" tblASCII[""] = "A" tblASCII[""] = "A" tblASCII[""] = "AE" tblASCII[""] = "C" tblASCII[""] = "E" tblASCII[""] = "E" tblASCII[""] = "E" tblASCII[""] = "E" tblASCII[""] = "I" tblASCII[""] = "I" tblASCII[""] = "I" tblASCII[""] = "I" tblASCII[""] = "D" tblASCII[""] = "N" tblASCII[""] = "O" tblASCII[""] = "O" tblASCII[""] = "O" tblASCII[""] = "O" tblASCII[""] = "O" tblASCII[""] = "*" tblASCII[""] = "O" tblASCII[""] = "U" tblASCII[""] = "U" tblASCII[""] = "U" tblASCII[""] = "U" tblASCII[""] = "Y" tblASCII[""] = "TH" tblASCII[""] = "ss" tblASCII[""] = "a" tblASCII[""] = "a" tblASCII[""] = "a" tblASCII[""] = "a" tblASCII[""] = "a" tblASCII[""] = "a" tblASCII[""] = "ae" tblASCII[""] = "c" tblASCII[""] = "e" tblASCII[""] = "e" tblASCII[""] = "e" tblASCII[""] = "e" tblASCII[""] = "i" tblASCII[""] = "i" tblASCII[""] = "i" tblASCII[""] = "i" tblASCII[""] = "d" tblASCII[""] = "n" tblASCII[""] = "o" tblASCII[""] = "o" tblASCII[""] = "o" tblASCII[""] = "o" tblASCII[""] = "o" tblASCII[""] = "/" tblASCII[""] = "o" tblASCII[""] = "u" tblASCII[""] = "u" tblASCII[""] = "u" tblASCII[""] = "u" tblASCII[""] = "y" tblASCII[""] = "th" tblASCII[""] = "y" -- Encode CP1252/ANSI characters into ASCII codes [\000-\127] -- function fh.StrANSI_ASCII(strText) return strEncode(strText,"[-]",tblASCII) end -- function StrANSI_ASCII --[=[ Unidecode converts each codepoint into a few ASCII characters. Lookup table indexed by codepoint [0x0000]-[0xFFFF] gives an ASCII string. i.e. strASCII = Unidecode[intByte2][intByte1] or "=?" allowing for partially populated table. See http://search.cpan.org/dist/Text-Unidecode/ and follow Browse to: See http://cpansearch.perl.org/src/SBURKE/Text-Unidecode-1.22/lib/Text/Unidecode/ where each x??.pm gives 256 ASCII conversions. Start with the first few European accented characters, and add the others later. --]=] local Unidecode = { } function fh.StrUnidecode(strChar1,strChar2) -- Decode UTF-16 byte pair into ASCII characters return Unidecode[string.byte(strChar2)][string.byte(strChar1)] or "=?" end -- function StrUnidecode -- Encode UTF-8 characters into ASCII codes [\000-\126] -- function fh.StrUTF8_ASCII(strText) strText = fh.StrUTF8_UTF16(strText) -- Convert to UTF-16 Unicode and then to ASCII return ( strText:gsub("(.)(.)",fh.StrUnidecode) ) end -- function StrUTF8_ASCII -- Encode CP1252/ANSI or UTF-8 into ASCII codes [\000-\126] -- function fh.StrEncode_ASCII(strText) if stringx.encoding() == "ANSI" then return fh.StrANSI_ASCII(strText) else return fh.StrUTF8_ASCII(strText) end end -- function StrEncode_ASCII -- Set markup language break tag -- function fh.SetBreakTag(br_New) if not (br_New or ""):match(br_Lua) then -- Ensure new break tag is "
" or "
" or "
" or "
" br_New = "
" end br_Tag = br_New end -- function SetBreakTag for intByte = 0x00, 0xFF do Unidecode[intByte] = { } end Unidecode[0x00] = {[0]="\00";"\01";"\02";"\03";"\04";"\05";"\06";"\a";"\b";"\t";"\n";"\v";"\f";"\r";"\14";"\15";"\16";"\17";"\18";"\19";"\20";"\21";"\22";"\23";"\24";"\25";"\26";"\27";"\28";"\29";"\30";"\31"; " ";"!";'"';"#";"$";"%";"&";"'";"(";")";"*";"+";",";"-";".";"/";"0";"1";"2";"3";"4";"5";"6";"7";"8";"9";":";";";"<";"=";">";"?"; -- 0x20 to 0x3F "@";"A";"B";"C";"D";"E";"F";"G";"H";"I";"J";"K";"L";"M";"N";"O";"P";"Q";"R";"S";"T";"U";"V";"W";"X";"Y";"Z";"[";"\\";"]";"^";"_"; -- 0x40 to 0x5F "`";"a";"b";"c";"d";"e";"f";"g";"h";"i";"j";"k";"l";"m";"n";"o";"p";"q";"r";"s";"t";"u";"v";"w";"x";"y";"z";"{";"|";"}";"~";"\127"; -- 0x60 to 0x7F ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; ""; -- 0x80 to 0x9F " ";"!";"=c";"=L";"=$";"=Y";"|";"=SS";'"';"(C)";"a";"<<";"-";"-";"(R)";"-";"=o";"+-";"2";"3";"'";"=u";"=P";"*";",";"1";"o";">>";"1/4";"1/2";"3/4";"?"; -- 0xA0 to 0xBF "A";"A";"A";"A";"A";"A";"AE";"C";"E";"E";"E";"E";"I";"I";"I";"I";"D";"N";"O";"O";"O";"O";"O";"*";"O";"U";"U";"U";"U";"Y";"TH";"ss"; -- 0xC0 to 0xDF "a";"a";"a";"a";"a";"a";"ae";"c";"e";"e";"e";"e";"i";"i";"i";"i";"d";"n";"o";"o";"o";"o";"o";"/";"o";"u";"u";"u";"u";"y";"th";"y"; -- 0xE0 to 0xFF } Unidecode[0x01] = {[0]="A";"a";"A";"a";"A";"a";"C";"c";"C";"c";"C";"c";"C";"c";"D";"d";"D";"d";"E";"e";"E";"e";"E";"e";"E";"e";"E";"e";"G";"g";"G";"g"; -- 0x00 to 0x1F "G";"g";"G";"g";"H";"h";"H";"h";"I";"i";"I";"i";"I";"i";"I";"i";"I";"i";"IJ";"ij";"J";"j";"K";"k";"k";"L";"l";"L";"l";"L";"l";"L"; -- 0x20 to 0x3F "l";"L";"l";"N";"n";"N";"n";"N";"n";"'n";"ng";"NG";"O";"o";"O";"o";"O";"o";"OE";"oe";"R";"r";"R";"r";"R";"r";"S";"s";"S";"s";"S";"s"; -- 0x40 to 0x5F "S";"s";"T";"t";"T";"t";"T";"t";"U";"u";"U";"u";"U";"u";"U";"u";"U";"u";"U";"u";"W";"w";"Y";"y";"Y";"Z";"z";"Z";"z";"Z";"z";"s"; -- 0x60 to 0x7F "b";"B";"B";"b";"6";"6";"O";"C";"c";"D";"D";"D";"d";"d";"3";"@";"E";"F";"f";"G";"G";"hv";"I";"I";"K";"k";"l";"l";"W";"N";"n";"O"; -- 0x80 to 0x9F "O";"o";"OI";"oi";"P";"p";"YR";"2";"2";"SH";"sh";"t";"T";"t";"T";"U";"u";"Y";"V";"Y";"y";"Z";"z";"ZH";"ZH";"zh";"zh";"2";"5";"5";"ts";"w"; -- 0xA0 to 0xBF "|";"||";"|=";"!";"DZ";"Dz";"dz";"LJ";"Lj";"lj";"NJ";"Nj";"nj";"A";"a";"I";"i";"O";"o";"U";"u";"U";"u";"U";"u";"U";"u";"U";"u";"@";"A";"a"; -- 0xC0 to 0xDF "A";"a";"AE";"ae";"G";"g";"G";"g";"K";"k";"O";"o";"O";"o";"ZH";"zh";"j";"DZ";"Dz";"dz";"G";"g";"HV";"W";"N";"n";"A";"a";"AE";"ae";"O";"o"; -- 0xE0 to 0xFF } Unidecode[0x02] = {[0]="A";"a";"A";"a";"E";"e";"E";"e";"I";"i";"I";"i";"O";"o";"O";"o";"R";"r";"R";"r";"U";"u";"U";"u";"S";"s";"T";"t";"Y";"y";"H";"h"; -- 0x00 to 0x1F "N";"d";"OU";"ou";"Z";"z";"A";"a";"E";"e";"O";"o";"O";"o";"O";"o";"O";"o";"Y";"y";"l";"n";"t";"j";"db";"qp";"A";"C";"c";"L";"T";"s"; -- 0x20 to 0x3F "z";"[?]";"[?]";"B";"U";"^";"E";"e";"J";"j";"q";"q";"R";"r";"Y";"y";"a";"a";"a";"b";"o";"c";"d";"d";"e";"@";"@";"e";"e";"e";"e";"j"; -- 0x40 to 0x5F "g";"g";"g";"g";"u";"Y";"h";"h";"i";"i";"I";"l";"l";"l";"lZ";"W";"W";"m";"n";"n";"n";"o";"OE";"O";"F";"r";"r";"r";"r";"r";"r";"r"; -- 0x60 to 0x7F "R";"R";"s";"S";"j";"S";"S";"t";"t";"u";"U";"v";"^";"w";"y";"Y";"z";"z";"Z";"Z";"?";"?";"?";"C";"@";"B";"E";"G";"H";"j";"k";"L"; -- 0x80 to 0x9F "q";"?";"?";"dz";"dZ";"dz";"ts";"tS";"tC";"fN";"ls";"lz";"WW";"]]";"h";"h";"h";"h";"j";"r";"r";"r";"r";"w";"y";"'";'"';"`";"'";"`";"`";"'"; -- 0xA0 to 0xBF "?";"?";"<";">";"^";"V";"^";"V";"'";"-";"/";"\\";",";"_";"\\";"/";":";".";"`";"'";"^";"V";"+";"-";"V";".";"@";",";"~";'"';"R";"X"; -- 0xC0 to 0xDF "G";"l";"s";"x";"?";"";"";"";"";"";"";"";"V";"=";'"';"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]"; -- 0xE0 to 0xFF } Unidecode[0x03] = { } Unidecode[0x04] = { } Unidecode[0x20] = {[0]=" ";" ";" ";" ";" ";" ";" ";" ";" ";" ";" ";" ";"";"";"";"";"-";"-";"-";"-";"--";"--";"||";"_";"'";"'";",";"'";'"';'"';",,";'"'; -- 0x00 to 0x1F "|+";"|++";"*";"*>";".";"..";"...";".";"\n";"\n\n";"";"";"";"";"";" ";"%0";"%00";"'";"''";"'''";"`";"``";"```";"^";"<";">";"*";"!!";"!?";"-";"_"; -- 0x20 to 0x3F "-";"^";"***";"--";"/";"-[";"]-";"[?]";"?!";"!?";"7";"PP";"(]";"[)";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]"; -- 0x40 to 0x5F "[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"";"";"";"";"";"";"0";"";"";"";"4";"5";"6";"7";"8";"9";"+";"-";"=";"(";")";"n"; -- 0x60 to 0x7F "0";"1";"2";"3";"4";"5";"6";"7";"8";"9";"+";"-";"=";"(";")";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]"; -- 0x80 to 0x9F "ECU";"CL";"Cr";"FF";"L";"mil";"N";"Pts";"Rs";"W";"NS";"D";"=E";"K";"T";"Dr";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]"; -- 0xA0 to 0xBF "[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"";"";"";"";"";"";"";"";"";"";"";"";"";"";"";""; -- 0xC0 to 0xDF "";"";"";"";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]";"[?]"; -- 0xE0 to 0xFF } Unidecode[0x21] = {[34]="TM"; } return fh end -- local function encoder_v3 local encoder = encoder_v3() -- To access FH encoder chars module --[[ @Module: +fh+progbar_v3 @Author: Mike Tate @Version: 3.0 @LastUpdated: 27 Aug 2020 @Description: Progress Bar library module. @V3.0: Function Prototype Closure version. @V1.0: Initial version. ]] local function progbar_v3() local fh = {} -- Local environment table require "iuplua" -- To access GUI window builder iup.SetGlobal("CUSTOMQUITMESSAGE","YES") -- Needed for IUP 3.28 local tblBars = {} -- Table for optional external attributes local strBack = "255 255 255" -- Background colour default is white local strBody = "0 0 0" -- Body text colour default is black local strFont = nil -- Font dialogue default is current font local strStop = "255 0 0" -- Stop button colour default is red local intPosX = iup.CENTER -- Show window default position is central local intPosY = iup.CENTER local intMax, intVal, intPercent, intStart, intDelta, intScale, strClock, isBarStop local lblText, barGauge, lblDelta, btnStop, dlgGauge local function doFocus() -- Bring the Progress Bar window into Focus dlgGauge.BringFront="YES" -- If used too often, inhibits other windows scroll bars, etc end -- local function doFocus local function doUpdate() -- Update the Progress Gauge and the Delta % with clock barGauge.Value = intVal lblDelta.Title = string.format("%4d %% %s ",math.floor(intPercent),strClock) end -- local function doUpdate local function doReset() -- Reset all dialogue variables and Update display intVal = 0 -- Current value of Progress Bar intPercent= 0.01 -- Percentage of progress intStart = os.time() -- Start time of progress intDelta = 0 -- Delta time of progress intScale = math.ceil( intMax / 1000 ) -- Scale of percentage per second of progress (initial guess is corrected in Step function) strClock = "00 : 00 : 00" -- Clock delta time display isBarStop = false -- Stop button pressed signal doUpdate() doFocus() end -- local function doReset function fh.Start(strTitle,intMaximum) -- Create & start Progress Bar window if not dlgGauge then strTitle = strTitle or "" -- Dialogue and button title intMax = intMaximum or 100 -- Maximun range of Progress Bar, default is 100 local strSize = tostring( math.max( 100, string.len(" Stop "..strTitle) * 8 ) ).."x30" -- Adjust Stop button size to Title lblText = iup.label { Title=" "; Expand="YES"; Alignment="ACENTER"; Tip="Progress Message"; } barGauge = iup.progressbar { RasterSize="400x30"; Value=0; Max=intMax; Tip="Progress Bar"; } lblDelta = iup.label { Title=" "; Expand="YES"; Alignment="ACENTER"; Tip="Percentage and Elapsed Time"; } btnStop = iup.button { Title=" Stop "..strTitle; RasterSize=strSize; FgColor=strStop; Tip="Stop Progress Button"; action=function() isBarStop = true end; } -- Signal Stop button pressed return iup.CLOSE -- Often caused main GUI to close !!! dlgGauge = iup.dialog { Title=strTitle.." Progress "; Font=strFont; FgColor=strBody; Background=strBack; DialogFrame="YES"; -- Remove Windows minimize/maximize menu iup.vbox{ Alignment="ACENTER"; Gap="10"; Margin="10x10"; lblText; barGauge; lblDelta; btnStop; }; move_cb = function(self,x,y) tblBars.X = x tblBars.Y = y end; close_cb = btnStop.action; -- Windows Close button = Stop button } if type(tblBars.GUI) == "table" and type(tblBars.GUI.ShowDialogue) == "function" then dlgGauge.move_cb = nil -- Use GUI library to show & move window tblBars.GUI.ShowDialogue("Bars",dlgGauge,btnStop,"showxy") else dlgGauge:showxy(intPosX,intPosY) -- Show the Progress Bar window end doReset() -- Reset the Progress Bar display end end -- function Start function fh.Message(strText) -- Show the Progress Bar message if dlgGauge then lblText.Title = strText end end -- function Message function fh.Step(intStep) -- Step the Progress Bar forward if dlgGauge then intVal = intVal + ( intStep or 1 ) -- Default step is 1 local intNew = math.ceil( intVal / intMax * 100 * intScale ) / intScale if intPercent ~= intNew then -- Update progress once per percent or per second, whichever is smaller intPercent = math.max( 0.1, intNew ) -- Ensure percentage is greater than zero if intVal > intMax then intVal = intMax intPercent = 100 end -- Ensure values do not exceed maximum intNew = os.difftime(os.time(),intStart) if intDelta < intNew then -- Update clock of elapsed time intDelta = intNew intScale = math.ceil( intDelta / intPercent ) -- Scale of seconds per percentage step local intHour = math.floor( intDelta / 3600 ) local intMins = math.floor( intDelta / 60 - intHour * 60 ) local intSecs = intDelta - intMins * 60 - intHour * 3600 strClock = string.format("%02d : %02d : %02d",intHour,intMins,intSecs) end doUpdate() -- Update the Progress Bar display end iup.LoopStep() end end -- function Step function fh.Focus() -- Bring the Progress Bar window to front if dlgGauge then doFocus() end end -- function Focus function fh.Reset() -- Reset the Progress Bar display if dlgGauge then doReset() end end -- function Reset function fh.Stop() -- Check if Stop button pressed iup.LoopStep() return isBarStop end -- function Stop function fh.Close() -- Close the Progress Bar window isBarStop = false if dlgGauge then dlgGauge:destroy() dlgGauge = nil end end -- function Close function fh.Setup(tblSetup) -- Setup optional table of external attributes if tblSetup then tblBars = tblSetup strBack = tblBars.Back or strBack -- Background colour strBody = tblBars.Body or strBody -- Body text colour strFont = tblBars.Font or strFont -- Font dialogue strStop = tblBars.Stop or strStop -- Stop button colour intPosX = tblBars.X or intPosX -- Window position intPosY = tblBars.Y or intPosY end end -- function Setup return fh end -- local function progbar_v3 local progbar = progbar_v3() -- To access FH progress bars module --[[ @Module: +fh+iup_gui_v3 @Author: Mike Tate @Version: 3.8 @LastUpdated: 13 Sep 2020 @Description: Graphical User Interface Library Module @V3.8: Function Prototype Closure version. @V3.7: AssignAttributes(tblControls) now allows any string attribute to invoke a function. @V3.6: anyMemoDialogue() sets TopMost attribute. @V3.5: Replace IsNormalWindow(iupDialog) with SetWindowCoord(tblName) and update CheckWindowPosition(tblName) to prevent negative values freezing main dialog. @V3.4: Use general.MakeFolder() to ensure key folders exist, add Get/PutRegKey(), check Registry IE Shell Version in HelpDialogue(), better error handling in LoadSettings(). @V3.3: LoadFolder() and SaveFolder() use global folder as default for local folder to improve synch across PC. @V3.2: Load & Save settings now use a single clipboard so Local PC settings are preserved across synchronised PC. @V3.1: IUP 3.11.2 iup.GetGlobal("VERSION") to top, HelpDialogue conditional ExpandChildren="YES/NO", RefreshDialogue uses NaturalSize, SetUtf8Mode(), Load/SaveFolder(), etc @V3.0: ShowDialogue "dialog" mode for Memo, new DestroyDialogue, NewHelpDialogue tblAttr for Font, AssignAttributes intSkip, CustomDialogue iup.CENTERPARENT+, IUP Workaround, BalloonToggle, Initialise test Plugin file exists. @V2.0: Support for Plugin Data scope, new FontDialogue, RefreshDialogue, AssignAttributes, httpRequest handler, keep "dialog" mode. @V1.0: Initial version. ]] local function iup_gui_v3() local fh = {} -- Local environment table require "iuplua" -- To access GUI window builder require "iupluacontrols" -- To access GUI window controls require "lfs" -- To access LUA filing system require "iupluaole" -- To access OLE subsystem require "luacom" -- To access COM subsystem iup.SetGlobal("CUSTOMQUITMESSAGE","YES") -- Needed for IUP 3.28 local iupVersion = iup.GetGlobal("VERSION") -- Obtain IUP module version -- "iuplua" Omitted Constants Workaround -- iup.TOP = iup.LEFT iup.BOTTOM = iup.RIGHT iup.RED = iup.RGB(1,0,0) iup.GREEN = iup.RGB(0,1,0) iup.BLUE = iup.RGB(0,0,1) iup.BLACK = iup.RGB(0,0,0) iup.WHITE = iup.RGB(1,1,1) iup.YELLOW = iup.RGB(1,1,0) -- Shared Interface Attributes & Functions -- fh.Version = " " -- Plugin Version fh.History = fh.Version -- Version History fh.Red = "255 0 0" -- Color attributes (must exclude leading zeros & spaces to allow value comparisons) fh.Maroon = "128 0 0" fh.Amber = "250 160 0" fh.Orange = "255 165 0" fh.Yellow = "255 255 0" fh.Olive = "128 128 0" fh.Lime = "0 255 0" fh.Green = "0 128 0" fh.Cyan = "0 255 255" fh.Teal = "0 128 128" fh.Blue = "0 0 255" fh.Navy = "0 0 128" fh.Magenta = "255 0 255" fh.Purple = "128 0 128" fh.Black = "0 0 0" fh.Gray = "128 128 128" fh.Silver = "192 192 192" fh.Smoke = "240 240 240" fh.White = "255 255 255" fh.Risk = fh.Red -- Risk colour for hazardous controls such as Close/Delete buttons fh.Warn = fh.Magenta -- Warn colour for caution controls and warnings fh.Safe = fh.Green -- Safe colour for active controls such as most buttons fh.Info = fh.Black -- Info colour for text controls such as labels/tabs fh.Head = fh.Black -- Head colour for headings fh.Body = fh.Black -- Body colour for body text fh.Back = fh.White -- Background colour for all windows fh.Gap = "8" -- Layout attributes Gap was "10" fh.Border = "8x8" -- was BigMargin="10x10" fh.Margin = "1x1" -- was MinMargin fh.Balloon = "NO" -- Tooltip balloon mode fh.FontSet = 0 -- Legacy GUI font set assigned by FontAssignment but used globally fh.FontHead = "" fh.FontBody = "" local GUI = { } -- Sub-table for GUI Dialogue attributes to allow any "Name" --[[ GUI.Name table of dialogue attributes, where Name is Font, Help, Main, Memo, Bars, etc GUI.Name.CoordX x co-ordinate ( Loaded & Saved by default ) GUI.Name.CoordY y co-ordinate ( Loaded & Saved by default ) GUI.Name.Dialog dialogue handle GUI.Name.Focus focus button handle GUI.Name.Frame dialogframe mode, "normal" = dialogframe="NO" else "YES", "showxy" = showxy(), "popup" or "keep" = popup(), default is "normal & showxy" GUI.Name.Height height GUI.Name.Raster rastersize ( Loaded & Saved by default ) GUI.Name.Width width GUI.Name.Back ProgressBar background colour GUI.Name.Body ProgressBar body text colour GUI.Name.Font ProgressBar font style GUI.Name.Stop ProgressBar Stop button colour GUI.Name.GUI Module table usable by other modules e.g. progbar.Setup Help dialogue Window attributes :- GUI.Help.GetHelp Parent dialogue GetHelp button GUI.Help.RootURL Wiki Help & Advice root URL GUI.Help.TblAttr Table of button attributes GUI.Help[n] Help dialogue nth button :- GUI.Help[n].Name Name for title attribute GUI.Help[n].Tip Tooltip for tip attribute GUI.Help[n].URL Page URL to append to root URL GUI.Help[n].Page Page order for intTabPosn --]] -- tblScrn[1] = origin x, tblScrn[2] = origin y, tblScrn[3] = width, tblScrn[4] = height local tblScrn = stringx.splitnumbers(iup.GetGlobal("VIRTUALSCREEN")) -- Used by CustomDialogue() and CheckWindowPosition() and ShowDialogue() below local intMaxW = tblScrn[3] local intMaxH = tblScrn[4] function fh.BalloonToggle() -- Toggle tooltips Balloon mode local tblToggle = { YES="NO"; NO="YES"; } fh.Balloon = tblToggle[fh.Balloon] fh.SaveSettings() end -- function BalloonToggle iup.SetGlobal("UTF8MODE","NO") function fh.SetUtf8Mode() -- Set IUP into UTF-8 display mode if iupVersion == "3.5" or stringx.encoding() == "ANSI" then return false end iup.SetGlobal("UTF8MODE","YES") iup.SetGlobal("UTF8MODE_FILE","NO") return true end -- function SetUtf8Mode local function tblOfNames(...) -- Get table of dialogue Names including "Font","Help","Main" by default local arg = {...} local tblNames = {"Font";"Help";"Main";} for intName, strName in ipairs(arg) do if type(strName) == "string" and strName ~= "Font" and strName ~= "Help" and strName ~= "Main" then table.insert(tblNames,strName) end end return tblNames end -- local function tblOfNames local function tblNameFor(strName) -- Get table of parameters for chosen dialogue Name strName = tostring(strName) if not GUI[strName] then -- Need new table with default minimum & raster size, and X & Y co-ordinates GUI[strName] = { } local tblName = GUI[strName] tblName.Raster = "x" tblName.CoordX = iup.CENTER tblName.CoordY = iup.CENTER end return GUI[strName] end -- local function tblNameFor local function intDimension(intMin,intVal,intMax) -- Return a number bounded by intMin and intMax if not intVal then return 0 end -- Except if no value then return 0 intVal = tonumber(intVal) or (intMin+intMax)/2 return math.max(intMin,math.min(intVal,intMax)) end -- local function intDimension function fh.CustomDialogue(strName,strRas,intX,intY) -- GUI custom window raster size, and X & Y co-ordinates -- strRas nil = old size, "x" or "0x0" = min size, "999x999" = new size -- intX/Y nil = central, "99" = co-ordinate position local tblName = tblNameFor(strName) local tblSize = {} local intWide = 0 local intHigh = 0 strRas = strRas or tblName.Raster if strRas then -- Ensure raster size is between minimum and screen size tblSize = stringx.splitnumbers(strRas) intWide = intDimension(intWide,tblSize[1],intMaxW) intHigh = intDimension(intHigh,tblSize[2],intMaxH) strRas = tostring(intWide.."x"..intHigh) end if intX and intX < iup.CENTERPARENT then intX = intDimension(0,intX,intMaxW-intWide) -- Ensure X co-ordinate positions window on screen end if intY and intY < iup.CENTERPARENT then intY = intDimension(0,intY,intMaxH-intHigh) -- Ensure Y co-ordinate positions window on screen end tblName.Raster = strRas or "x" tblName.CoordX = tonumber(intX) or iup.CENTER tblName.CoordY = tonumber(intY) or iup.CENTER end -- function CustomDialogue function fh.DefaultDialogue(...) -- GUI default window minimum & raster size, and X & Y co-ordinates for intName, strName in ipairs(tblOfNames(...)) do fh.CustomDialogue(strName) end end -- function DefaultDialogue function fh.DialogueAttributes(strName) -- Provide named Dialogue Attributes local tblName = tblNameFor(strName) -- tblName.Dialog = dialog handle, so any other attributes could be retrieved local tblSize = stringx.splitnumbers(tblName.Raster or "x") -- Split Raster Size into width=tblSize[1] and height=tblSize[2] tblName.Width = tblSize[1] tblName.Height= tblSize[2] tblName.Back = fh.Back -- Following only needed for NewProgressBar tblName.Body = fh.Body tblName.Font = fh.FontBody tblName.Stop = fh.Risk tblName.GUI = fh -- Module table return tblName end -- function DialogueAttributes local strDefaultScope = "Project" -- Default scope for Load/Save data is per Project/User/Machine as set by PluginDataScope() local tblClipProj = { } local tblClipUser = { } -- Clipboards of sticky data for each Plugin Data scope -- V3.2 local tblClipMach = { } local function doLoadData(strParam,strDefault,strScope) -- Load sticky data for Plugin Data scope strScope = tostring(strScope or strDefaultScope):lower() local tblClipData = tblClipProj if strScope:match("user") then tblClipData = tblClipUser elseif strScope:match("mach") then tblClipData = tblClipMach end return tblClipData[strParam] or strDefault end -- local function doLoadData function fh.LoadGlobal(strParam,strDefault,strScope) -- Load Global Parameter for all PC return doLoadData(strParam,strDefault,strScope) end -- function LoadGlobal function fh.LoadLocal(strParam,strDefault,strScope) -- Load Local Parameter for this PC return doLoadData(fh.ComputerName.."-"..strParam,strDefault,strScope) end -- function LoadLocal local function doLoadFolder(strFolder) -- Use relative paths to let Paths change -- V3.3 strFolder = strFolder:gsub("^FhDataPath",function() return fh.FhDataPath end) -- Full path to .fh_data folder strFolder = strFolder:gsub("^PublicPath",function() return fh.PublicPath end) -- Full path to Public folder strFolder = strFolder:gsub("^FhProjPath",function() return fh.FhProjPath end) -- Full path to project folder return strFolder end -- local function doLoadFolder function fh.LoadFolder(strParam,strDefault,strScope) -- Load Folder Parameter for this PC -- V3.3 local strFolder = doLoadFolder(fh.LoadLocal(strParam,"",strScope)) if not general.FlgFolderExists(strFolder) then -- If no local folder try global folder strFolder = doLoadFolder(fh.LoadGlobal(strParam,strDefault,strScope)) end return strFolder end -- function LoadFolder function fh.LoadDialogue(...) -- Load Dialogue Parameters for "Font","Help","Main" by default for intName, strName in ipairs(tblOfNames(...)) do local tblName = tblNameFor(strName) --# tblName.Raster = tostring(fh.LoadLocal(strName.."S",tblName.Raster)) -- Legacy of "S" becomes "R" tblName.Raster = tostring(fh.LoadLocal(strName.."R",tblName.Raster)) tblName.CoordX = tonumber(fh.LoadLocal(strName.."X",tblName.CoordX)) tblName.CoordY = tonumber(fh.LoadLocal(strName.."Y",tblName.CoordY)) fh.CheckWindowPosition(tblName) end end -- function LoadDialogue function fh.LoadSettings(...) -- Load Sticky Settings from File for strFileName, tblClipData in pairs ({ ProjectFile=tblClipProj; PerUserFile=tblClipUser; MachineFile=tblClipMach; }) do strFileName = fh[strFileName] if general.FlgFileExists(strFileName) then -- Load Settings File in table lines with key & val fields local tblField = {} for strLine in io.lines(strFileName) do if #tblField == 0 and strLine == "return {" -- Unless entire Sticky Data table was saved and type(table.load) == "function" then local tblClip, strErr = table.load(strFileName) -- Load Settings File table if strErr then error(strErr.."\n\nMay need to Delete the following Plugin Data .dat file:\n\n"..strFileName.."\n\nError detected.") end for i,j in pairs (tblClip) do tblClipData[i] = tblClip[i] end break end tblField = stringx.split(strLine,"=") if tblField[1] then tblClipData[tblField[1]] = tblField[2] end end end end fh.Safe = tostring(fh.LoadGlobal("SafeColor",fh.Safe)) fh.Warn = tostring(fh.LoadGlobal("WarnColor",fh.Warn)) fh.Risk = tostring(fh.LoadGlobal("RiskColor",fh.Risk)) fh.Head = tostring(fh.LoadGlobal("HeadColor",fh.Head)) fh.Body = tostring(fh.LoadGlobal("BodyColor",fh.Body)) fh.FontHead= tostring(fh.LoadGlobal("FontHead" ,fh.FontHead)) fh.FontBody= tostring(fh.LoadGlobal("FontBody" ,fh.FontBody)) fh.FontSet = tonumber(fh.LoadGlobal("Fonts" ,fh.FontSet)) -- Legacy only fh.FontSet = tonumber(fh.LoadGlobal("FontSet" ,fh.FontSet)) -- Legacy only fh.History = tostring(fh.LoadGlobal("History" ,fh.History)) fh.Balloon = tostring(fh.LoadGlobal("Balloon" ,fh.Balloon, "Machine")) fh.LoadDialogue(...) if fh.FontSet > 0 then fh.FontAssignment(fh.FontSet) end -- Legacy only end -- function LoadSettings local function doSaveData(strParam,anyValue,strScope) -- Save sticky data for Plugin Data scope strScope = tostring(strScope or strDefaultScope):lower() local tblClipData = tblClipProj if strScope:match("user") then tblClipData = tblClipUser elseif strScope:match("mach") then tblClipData = tblClipMach end tblClipData[strParam] = anyValue end -- local function doSaveData function fh.SaveGlobal(strParam,anyValue,strScope) -- Save Global Parameter for all PC doSaveData(strParam,anyValue,strScope) end -- function SaveGlobal function fh.SaveLocal(strParam,anyValue,strScope) -- Save Local Parameter for this PC doSaveData(fh.ComputerName.."-"..strParam,anyValue,strScope) end -- function SaveLocal function fh.SaveFolder(strParam,strFolder,strScope) -- Save Folder Parameter for this PC strFolder = stringx.replace(strFolder,fh.FhDataPath,"FhDataPath") -- Full path to .fh_data folder strFolder = stringx.replace(strFolder,fh.PublicPath,"PublicPath") -- Full path to Public folder strFolder = stringx.replace(strFolder,fh.FhProjPath,"FhProjPath") -- Full path to project folder --# doSaveData(fh.ComputerName.."-"..strParam,strFolder,strScope) -- Uses relative paths to let Paths change fh.SaveGlobal(strParam,strFolder,strScope) -- V3.3 fh.SaveLocal(strParam,strFolder,strScope) -- Uses relative paths to let Paths change end -- function SaveFolder function fh.SaveDialogue(...) -- Save Dialogue Parameters for "Font","Help","Main" by default for intName, strName in ipairs(tblOfNames(...)) do local tblName = tblNameFor(strName) fh.SaveLocal(strName.."R",tblName.Raster) fh.SaveLocal(strName.."X",tblName.CoordX) fh.SaveLocal(strName.."Y",tblName.CoordY) end end -- function SaveDialogue function fh.SaveSettings(...) -- Save Sticky Settings to File fh.SaveDialogue(...) fh.SaveGlobal("SafeColor",fh.Safe) fh.SaveGlobal("WarnColor",fh.Warn) fh.SaveGlobal("RiskColor",fh.Risk) fh.SaveGlobal("HeadColor",fh.Head) fh.SaveGlobal("BodyColor",fh.Body) fh.SaveGlobal("FontHead" ,fh.FontHead) fh.SaveGlobal("FontBody" ,fh.FontBody) fh.SaveGlobal("History" ,fh.History) fh.SaveGlobal("Balloon" ,fh.Balloon, "Machine") for strFileName, tblClipData in pairs ({ ProjectFile=tblClipProj; PerUserFile=tblClipUser; MachineFile=tblClipMach; }) do for i,j in pairs (tblClipData) do -- Check if table has any entries strFileName = fh[strFileName] if type(table.save) == "function" then -- Save entire Settings File table per Project/User/Machine table.save(tblClipData,strFileName) else local fileHandle = general.OpenFile(strFileName,"w") -- Else save Settings File lines with key & val fields for strKey,strVal in pairs(tblClipData) do fileHandle:write(strKey.."="..strVal.."\n") end fileHandle:close() end break end end end -- function SaveSettings function fh.CheckWindowPosition(tblName) -- Ensure dialogue window coordinates are on Screen if tonumber(tblName.CoordX) == nil or tonumber(tblName.CoordX) < 0 -- V3.5 or tonumber(tblName.CoordX) > intMaxW then tblName.CoordX = iup.CENTER end if tonumber(tblName.CoordY) == nil or tonumber(tblName.CoordY) < 0 -- V3.5 or tonumber(tblName.CoordY) > intMaxH then tblName.CoordY = iup.CENTER end end -- function CheckWindowPosition function fh.IsNormalWindow(iupDialog) -- Check dialogue window is not Maximised or Minimised (now redundant) -- tblPosn[1] = origin x, tblPosn[2] = origin y, tblPosn[3] = width, tblPosn[4] = height local tblPosn = stringx.splitnumbers(iupDialog.ScreenPosition) local intPosX = tblPosn[1] local intPosY = tblPosn[2] if intPosX < 0 and intPosY < 0 then -- If origin is negative (-8, -8 = Maximised, -3200, -3200 = Minimised) return false -- then is Maximised or Minimised end return true end -- function IsNormalWindow function fh.SetWindowCoord(tblName) -- Set the Window coordinates if not Maximised or Minimised -- V3.5 -- tblPosn[1] = origin x, tblPosn[2] = origin y, tblPosn[3] = width, tblPosn[4] = height local tblPosn = stringx.splitnumbers(tblName.Dialog.ScreenPosition) local intPosX = tblPosn[1] local intPosY = tblPosn[2] if intPosX < 0 and intPosY < 0 then -- If origin is negative (-8, -8 = Maximised, -3200, -3200 = Minimised) return false -- then is Maximised or Minimised end tblName.CoordX = intPosX -- Otherwise set the Window coordinates tblName.CoordY = intPosY return true end -- function SetWindowCoord function fh.ShowDialogue(strName,iupDialog,btnFocus,strFrame) -- Set standard frame attributes and display dialogue window local tblName = tblNameFor(strName) iupDialog = iupDialog or tblName.Dialog -- Retrieve previous parameters if needed btnFocus = btnFocus or tblName.Focus strFrame = strFrame or tblName.Frame strFrame = strFrame or "show norm" -- Default frame mode is dialog:showxy(X,Y) with DialogFrame="NO" ("normal" to vary size, otherwise fixed size) strFrame = strFrame:lower() -- Other modes are "show", "popup" & "keep" with DialogFrame="YES", or with "normal" for DialogFrame="NO" ("show" for active windows, "popup"/"keep" for modal windows) if strFrame:gsub("%s-%a-map%a*[%s%p]*","") == "" then -- May be prefixed with "map" mode to just map dialogue initially, also may be suffixed with "dialog" to inhibit iup.MainLoop() to allow progress messages strFrame = "map show norm" -- If only "map" mode then default to "map show norm" end if type(iupDialog) == "userdata" then tblName.Dialog = iupDialog tblName.Focus = btnFocus -- Preserve parameters tblName.Frame = strFrame iupDialog.Background = fh.Back -- Background colour iupDialog.Shrink = "YES" -- Sometimes needed to shrink controls to raster size if type(btnFocus) == "userdata" then -- Set button as focus for Esc and Enter keys iupDialog.StartFocus = iupDialog.StartFocus or btnFocus iupDialog.DefaultEsc = iupDialog.DefaultEsc or btnFocus iupDialog.DefaultEnter = iupDialog.DefaultEnter or btnFocus end iupDialog.MaxSize = intMaxW.."x"..intMaxH -- Maximum size is screen size iupDialog.MinSize = "x" -- Minimum size (default "x" becomes nil) iupDialog.RasterSize = tblName.Raster or "x" -- Raster size (default "x" becomes nil) if strFrame:match("norm") then -- DialogFrame mode is "NO" by default for variable size window if strFrame:match("pop") or strFrame:match("keep") then iupDialog.MinBox = "NO" -- For "popup" and "keep" hide Minimize and Maximize icons iupDialog.MaxBox = "NO" else strFrame = strFrame.." show" -- If not "popup" nor "keep" then use "showxy" mode end else iupDialog.DialogFrame = "YES" -- Define DialogFrame mode for fixed size window end iupDialog.close_cb = iupDialog.close_cb or function() return iup.CLOSE end -- Define default window X close, move, and resize actions iupDialog.move_cb = iupDialog.move_cb or function(self) fh.SetWindowCoord(tblName) end -- V3.5 iupDialog.resize_cb = iupDialog.resize_cb or function(self) if fh.SetWindowCoord(tblName) then tblName.Raster=self.RasterSize end end -- V3.5 if strFrame:match("map") then -- Only dialogue mapping is required iupDialog:map() --# tblName.Frame = strFrame:gsub("map","") -- Remove "map" from frame mode ready for subsequent call tblName.Frame = strFrame:gsub("%s-%a-map%a*[%s%p]*","") -- Remove "map" from frame mode ready for subsequent call return end fh.RefreshDialogue(strName) -- Refresh to set Natural Size as Minimum Size if iup.MainLoopLevel() == 0 -- Called from outside Main GUI, so must use showxy() and not popup() or strFrame:match("dialog") or strFrame:match("sho") then -- Use showxy() to dispay dialogue window for "showxy" or "dialog" mode iupDialog:showxy(tblName.CoordX,tblName.CoordY) if fh.History ~= fh.Version then -- Initially show new Version History Help if type(fh.HelpDialogue) == "function" then fh.History = fh.Version fh.HelpDialogue(fh.Version) -- But only after Help dialogue exists iupDialog.BringFront = "YES" end end if not ( strName == "Help" or strFrame:match("dialog") ) -- Inhibit MainLoop if Help dialogue or "dialog" mode and iup.MainLoopLevel() == 0 then iup.MainLoop() end else iupDialog:popup(tblName.CoordX,tblName.CoordY) -- Use popup() to display dialogue window for "popup" or "keep" modes end if not strFrame:match("dialog") and strFrame:match("pop") then tblName.Dialog = nil -- When popup closed, clear key parameters, but not for "keep" mode tblName.Raster = nil tblName.CoordX = nil -- iup.CENTER tblName.CoordY = nil -- iup.CENTER else fh.SetWindowCoord(tblName) -- Set Window coordinate pixel values -- V3.5 end end end -- function ShowDialogue function fh.DestroyDialogue(strName) -- Destroy existing dialogue local tblName = tblNameFor(strName) if tblName then local iupDialog = tblName.Dialog if type(iupDialog) == "userdata" then iupDialog:destroy() tblName.Dialog = nil -- Prevent future misuse of handle -- 22 Jul 2014 end end end -- function DestroyDialogue local function strDialogueArgs(strArgA,strArgB,comp) -- Compare two argument pairs and return matching pair local tblArgA = stringx.splitnumbers(strArgA) local tblArgB = stringx.splitnumbers(strArgB) local strArgX = tostring(comp(tblArgA[1] or 100,tblArgB[1] or 100)) local strArgY = tostring(comp(tblArgA[2] or 100,tblArgB[2] or 100)) return strArgX.."x"..strArgY end -- local function strDialogueArgs function fh.RefreshDialogue(strName) -- Refresh dialogue window size after Font change, etc local tblName = tblNameFor(strName) local iupDialog = tblName.Dialog -- Retrieve the dialogue handle if type(iupDialog) == "userdata" then iupDialog.Size = iup.NULL iupDialog.MinSize = iup.NULL -- V3.1 iup.Refresh(iupDialog) -- Refresh window to Natural Size and set as Minimum Size if not iupDialog.RasterSize then iupDialog:map() iup.Refresh(iupDialog) end local strSize = iupDialog.NaturalSize or iupDialog.RasterSize -- IUP 3.5 NaturalSize = nil, IUP 3.11 needs NaturalSize -- V3.1 iupDialog.MinSize = strDialogueArgs(iupDialog.MaxSize,strSize,math.min) -- Set Minimum Size to smaller of Maximm Size or Natural/Raster Size -- V3.1 iupDialog.RasterSize = strDialogueArgs(tblName.Raster,strSize,math.max) -- Set Current Size to larger of Current Size or Natural/Raster Size -- V3.1 iup.Refresh(iupDialog) tblName.Raster = iupDialog.RasterSize if iupDialog.Visible == "YES" then -- Ensure visible dialogue origin is on screen tblName.CoordX = math.max(tblName.CoordX,10) tblName.CoordY = math.max(tblName.CoordY,10) -- Set both coordinates to larger of current value or 10 pixels if iupDialog.Modal then -- V3.8 if iupDialog.Modal == "NO" then iupDialog.ZOrder = "BOTTOM" -- Ensure dialogue is subservient to any popup iupDialog:showxy(tblName.CoordX,tblName.CoordY) -- Use showxy() to reposition main window else iupDialog:popup(tblName.CoordX,tblName.CoordY) -- Use popup() to reposition modal window end end else iupDialog.BringFront="YES" end end end -- function RefreshDialogue function fh.AssignAttributes(tblControls) -- Assign the attributes of all controls supplied local anyFunction = nil for iupName, tblAttr in pairs ( tblControls or {} ) do if type(iupName) == "userdata" and type(tblAttr) == "table" then-- Loop through each iup control local intSkip = 0 -- Skip counter for attributes same for all controls for intAttr, anyName in ipairs ( tblControls[1] or {} ) do -- Loop through each iup attribute local strName = nil local strAttr = nil local strType = type(anyName) if strType == "string" then -- Attribute is different for each control in tblControls strName = anyName strAttr = tblAttr[intAttr-intSkip] elseif strType == "table" then -- Attribute is same for all controls as per tblControls[1] intSkip = intSkip + 1 strName = anyName[1] strAttr = anyName[2] elseif strType == "function" then intSkip = intSkip + 1 anyFunction = anyName break end if type(strName) == "string" and ( type(strAttr) == "string" or type(strAttr) == "function" ) then local anyRawGet = rawget(fh,strAttr) -- Use rawget() to stop require("pl.strict") complaining if type(anyRawGet) == "string" then strAttr = anyRawGet -- Use internal module attribute such as Head or FontBody elseif type(iupName[strName]) == "string" and type(strAttr) == "function" then -- Allow string attributes to invoke a function -- V3.7 strAttr = strAttr() end iupName[strName] = strAttr -- Assign attribute to control end end end end if anyFunction then anyFunction() end -- Perform any control assignment function end -- function AssignAttributes -- Font Dialogue Attributes and Functions -- fh.FontBody = iup.GetGlobal("DEFAULTFONT") -- Set default font for Body and Head text fh.FontHead = fh.FontBody:gsub(", B?o?l?d?",", Bold ") ---[=[ local intFontPlain = 1 -- Font Face & Style values for legacy FontSet setting local intFontBold = 2 local intArialPlain = 3 local intArialBold = 4 local intTahomaPlain= 5 local intTahomaBold = 6 local strFontFace = fh.FontBody:gsub(",.*","") local tblFontSet = {} -- Lookup table for FontHead and FontBody tblFontSet[intFontPlain] = { Head=strFontFace.."; Bold -16"; Body=strFontFace.."; -15"; } tblFontSet[intFontBold] = { Head=strFontFace.."; Bold -16"; Body=strFontFace.."; Bold -15"; } tblFontSet[intArialPlain] = { Head="Arial; Bold -16"; Body="Arial; -16"; } tblFontSet[intArialBold] = { Head="Arial; Bold -16"; Body="Arial; Bold -15"; } tblFontSet[intTahomaPlain] = { Head="Tahoma; Bold -15"; Body="Tahoma; -16"; } tblFontSet[intTahomaBold] = { Head="Tahoma; Bold -15"; Body="Tahoma; Bold -14"; } function fh.FontAssignment(intFontSet) -- Assign Font Face & Style GUI values for legacy FontSet setting if intFontSet then intFontSet = math.max(intFontSet,1) intFontSet = math.min(intFontSet,#tblFontSet) fh.FontHead = tblFontSet[intFontSet]["Head"] -- Legacy Font for all GUI dialog header text fh.FontBody = tblFontSet[intFontSet]["Body"] -- Legacy Font for all GUI dialog body text end end -- function FontAssignment --]=] function fh.FontDialogue(tblAttr,strName) -- GUI Font Face & Style Dialogue tblAttr = tblAttr or {} strName = strName or "Main" local isFontChosen = false local btnFontHead = iup.button { Title="Choose Headings Font and default Colour"; } local btnFontBody = iup.button { Title="Choose Body text Font and default Colour"; } local btnCol_Safe = iup.button { Title=" Safe Colour "; } local btnCol_Warn = iup.button { Title=" Warning Colour "; } local btnCol_Risk = iup.button { Title=" Risky Colour "; } local btnDefault = iup.button { Title=" Default Fonts "; } local btnMinimum = iup.button { Title=" Minimum Size "; } local btnDestroy = iup.button { Title=" Close Dialogue "; } local frmSetFonts = iup.frame { Title=" Set Window Fonts & Colours "; iup.vbox { Alignment="ACENTER"; Margin=fh.Margin; Homogeneous="YES"; btnFontHead; btnFontBody; iup.hbox { btnCol_Safe; btnCol_Warn; btnCol_Risk; Homogeneous="YES"; }; iup.hbox { btnDefault ; btnMinimum ; btnDestroy ; Homogeneous="YES"; }; } -- iup.vbox end } -- iup.frame end -- Create dialogue and turn off resize, maximize, minimize, and menubox except Close button local dialogFont = iup.dialog { Title=" Set Window Fonts & Colours "; Gap=fh.Gap; Margin=fh.Border; frmSetFonts; } local tblButtons = { } local function setDialogues() -- Refresh the Main and Help dialogues local tblHelp = tblNameFor("Help") if type(tblHelp.Dialog) == "userdata" then -- Help dialogue exists fh.AssignAttributes(tblHelp.TblAttr) -- Assign the Help dialogue attributes fh.RefreshDialogue("Help") -- Refresh the Help window size & position end fh.AssignAttributes(tblAttr) -- Assign parent dialogue attributes fh.RefreshDialogue(strName) -- Refresh parent window size & position and bring infront of Help window fh.RefreshDialogue("Font") -- Refresh Font window size & position and bring infront of parent window end -- local function setDialogues local function getFont(strColor) -- Set font button function local strTitle = " Choose font style & default colour for "..strColor:gsub("Head","Heading").." text " local strValue = "Font"..strColor -- The font codes below are not recognised by iupFontDlg and result in empty font face! local strFont = rawget(fh,strValue):gsub(" Black,",","):gsub(" Light, Bold",","):gsub(" Extra Bold,",","):gsub(" Semibold,",",") local iupFontDlg = iup.fontdlg { Title=strTitle; Color=rawget(fh,strColor); Value=strFont; } iupFontDlg:popup() -- Popup predefined font dialogue if iupFontDlg.Status == "1" then if iupFontDlg.Value:match("^,") then -- Font face missing so revert to original font iupFontDlg.Value = rawget(fh,strValue) end fh[strColor] = iupFontDlg.Color -- Set Head or Body color attribute fh[strValue] = iupFontDlg.Value -- Set FontHead or FontBody font style fh.AssignAttributes(tblButtons) -- Assign the button & frame attributes setDialogues() isFontChosen = true end end -- local function getFont local function getColor(strColor) -- Set colour button function local strTitle = " Choose colour for "..strColor:gsub("Warn","Warning"):gsub("Risk","Risky").." button & message text " local iupColorDlg = iup.colordlg { Title=strTitle; Value=rawget(fh,strColor); ShowColorTable="YES"; } iupColorDlg.DialogFrame="YES" iupColorDlg:popup() -- Popup predefined color dialogue fixed size window if iupColorDlg.Status == "1" then fh[strColor] = iupColorDlg.Value -- Set Safe or Warn or Risk color attribute fh.AssignAttributes(tblButtons) -- Assign the button & frame attributes setDialogues() isFontChosen = true end end -- local function getColor local function setDefault() -- Action for Default Fonts button fh.Safe = fh.Green fh.Warn = fh.Magenta fh.Risk = fh.Red -- Set default colours fh.Body = fh.Black fh.Head = fh.Black fh.FontBody = iup.GetGlobal("DEFAULTFONT") -- Set default fonts for Body and Head text fh.FontHead = fh.FontBody:gsub(", B?o?l?d?",", Bold") fh.AssignAttributes(tblButtons) -- Assign the button & frame attributes setDialogues() isFontChosen = true end -- local function setDefault local function setMinimum() -- Action for Minimum Size button local tblName = tblNameFor(strName) local iupDialog = tblName.Dialog -- Retrieve the parent dialogue handle if type(iupDialog) == "userdata" then tblName.Raster = "10x10" -- Refresh parent window to Minimum Size & adjust position fh.RefreshDialogue(strName) end local tblFont = tblNameFor("Font") tblFont.Raster = "10x10" -- Refresh Font window to Minimum Size & adjust position fh.RefreshDialogue("Font") end -- local function setMinimum tblButtons = { { "Font" ; "FgColor" ; "Tip" ; "action" ; {"TipBalloon";"Balloon";} ; {"Expand";"YES";} ; }; [btnFontHead] = { "FontHead"; "Head"; "Choose the Heading text Font Face, Style, Size, Effects, and default Colour"; function() getFont("Head") end; }; [btnFontBody] = { "FontBody"; "Body"; "Choose the Body text Font Face, Style, Size, Effects, and default Colour" ; function() getFont("Body") end; }; [btnCol_Safe] = { "FontBody"; "Safe"; "Choose the colour for Safe operations" ; function() getColor("Safe") end; }; [btnCol_Warn] = { "FontBody"; "Warn"; "Choose the colour for Warning operations"; function() getColor("Warn") end; }; [btnCol_Risk] = { "FontBody"; "Risk"; "Choose the colour for Risky operations" ; function() getColor("Risk") end; }; [btnDefault ] = { "FontBody"; "Safe"; "Restore default Fonts and Colours"; function() setDefault() end; }; [btnMinimum ] = { "FontBody"; "Safe"; "Reduce window to its minimum size"; function() setMinimum() end; }; [btnDestroy ] = { "FontBody"; "Risk"; "Close this dialogue "; function() return iup.CLOSE end; }; [frmSetFonts] = { "FontHead"; "Head"; }; } fh.AssignAttributes(tblButtons) -- Assign the button & frame attributes fh.ShowDialogue("Font",dialogFont,btnDestroy,"keep normal") -- Popup the Set Window Fonts dialogue: "keep normal" : vary size & posn, and remember size & posn -- fh.ShowDialogue("Font",dialogFont,btnDestroy,"popup normal") -- Popup the Set Window Fonts dialogue: "popup normal" : vary size & posn, but redisplayed centred -- fh.ShowDialogue("Font",dialogFont,btnDestroy,"keep") -- Popup the Set Window Fonts dialogue: "keep" : fixed size, vary posn, and only remember posn -- fh.ShowDialogue("Font",dialogFont,btnDestroy,"popup") -- Popup the Set Window Fonts dialogue: "popup": fixed size, vary posn, but redisplayed centred dialogFont:destroy() return isFontChosen end -- function FontDialogue -- Help Dialogue Attributes and Functions fh.HelpDialogue = "" -- HelpDialogue must be declared for ShowDialogue local strHelpButtonActive = nil -- defaults to "YES" -- Help button active attribute mode used only in NewHelpDialogue function fh.NewHelpDialogue(btnGetHelp,strRootURL) -- Prototype for GUI Help Dialogue, with parent Help button, and web page root/namespace URL local tblHelp = tblNameFor("Help") local oleControl, btnDestroy, hboxHelp, dialogHelp, tblAttr -- Dialogue component upvalues if type(btnGetHelp) == "userdata" then btnGetHelp.Active = strHelpButtonActive if btnGetHelp.Active == "NO" then -- Help button inactive, so Help dialogue exists, so just update parent button tblHelp.GetHelp = btnGetHelp -- Allows successive parent GUI to share one Help dialogue return end end tblHelp.GetHelp = btnGetHelp strRootURL = strRootURL or fh.Plugin:gsub(" ","_"):lower() -- Default to Plugin name as Wiki namespace if strRootURL:match("^[%w_]+$") then -- Append Wiki namespace to Wiki root URL strRootURL = "http://www.fhug.org.uk/wiki/doku.php?id=plugins:help:"..strRootURL..":" end tblHelp.RootURL = strRootURL local intURL = 1 -- Index to Version History help page URL local tblURL = { } -- List of help page URL local tblAttr = { } -- Attribute table primarily for FontDialogue() tblHelp.TblAttr = tblAttr local function doCommonAction() -- Common action when creating/destroying Help dialogue local strMode = "NO" if tblHelp.Dialog then tblHelp.Dialog = nil -- Clear dialog handle strMode = nil -- Defaults to "YES" but more efficient to test else tblAttr = { {"Font";"FgColor";}; } -- Reset attribute table primarily for FontDialogue() tblHelp.TblAttr = tblAttr end if type(tblHelp.GetHelp) == "userdata" then -- Set parent dialogue Help button active mode tblHelp.GetHelp.Active = strMode end strHelpButtonActive = strMode end -- local function doCommonAction -- Save global Help button active mode function fh.HelpDialogue(anyPage) -- GUI Help Dialogue for chosen web page --[=[ Parameter anyPage can be one of several values: 1. Page number from 0 to index tblURL, often equal to intTabPosn. 2. Version to display Version History page for version chosen. 3. String with " "="_" and lowercase substring of a page name in tblURL. --]=] if not fh.GetRegKey("HKLM\\SOFTWARE\\Microsoft\\Internet Explorer\\MAIN\\FeatureControl\\FEATURE_BROWSER_EMULATION\\fh.exe") then fhMessageBox("\n The 'Help and Advice' web page has encountered a problem. \n\n The FH IE Shell version is undefined in the Windows Registry. \n\n So please run the 'Write Reg IE Shell Version' Plugin to rectify. \n") return end if not tblHelp.Dialog then doCommonAction() -- Create the WebBrowser based on its ProgID and connect it to LuaCOM oleControl = iup.olecontrol{ "Shell.Explorer.1"; designmode="NO"; } oleControl:CreateLuaCOM() btnDestroy = iup.button { Title="Close Window"; Tip="Close this Help and Advice window"; TipBalloon=fh.Balloon; Expand="HORIZONTAL"; Size="x10"; FgColor=fh.Risk; action=function() dialogHelp:destroy() doCommonAction() end; } hboxHelp = iup.hbox { Margin=fh.Margin; Homogeneous="NO"; } -- Create each GUI button with title, tooltip, color, action, etc, and table of web page URL for intButton, tblButton in ipairs(tblHelp) do local intPage = tblButton.Page or intButton local strURL = tblButton.URL if strURL:match("ver.-hist") then intURL = intPage end tblURL[intPage] = strURL local btnName = iup.button { Title=tblButton.Name; Tip=tblButton.Tip; TipBalloon=fh.Balloon; Expand=btnDestroy.Expand; Size=btnDestroy.Size; FgColor=fh.Safe; action=function() oleControl.com:Navigate(tblHelp.RootURL..strURL) end; } iup.Append(hboxHelp,btnName) tblAttr[btnName] = { "FontBody"; "Safe"; } end iup.Append(hboxHelp,btnDestroy) tblAttr[btnDestroy] = { "FontBody"; "Risk"; } local strExpChild = "NO" if iupVersion == "3.5" then strExpChild = "YES" end -- V3.1 for IUP 3.11.2 dialogHelp = iup.dialog { Title=fh.Plugin.." Help & Advice"; Font=fh.FontBody; iup.vbox { Alignment="ACENTER"; Gap=fh.Gap; Margin=fh.Border; ExpandChildren=strExpChild; -- V3.1 for IUP 3.11.2 oleControl; hboxHelp; }; close_cb = function() doCommonAction() end; } fh.ShowDialogue("Help",dialogHelp,btnDestroy) -- Show Help dialogue window and set tblHelp.Dialog = dialogHelp end anyPage = anyPage or 0 if type(anyPage) == "number" then -- Select page by Tab = Button = Help page index anyPage = math.max(1,math.min(#tblURL,anyPage+1)) anyPage = tblURL[anyPage] or "" elseif anyPage == fh.Version then -- Select the Version History features section anyPage = anyPage:gsub("[%s%p]","") anyPage = anyPage:gsub("^(%d)","V%1") anyPage = tblURL[intURL].."#features_of_"..anyPage elseif type(anyPage) == "string" then -- Select page by matching name text local strPage = anyPage:gsub(" ","_"):lower() anyPage = tblURL[1] or "" -- Default to first web page for intURL = 1, #tblURL do local strURL = tblURL[intURL] if strURL:match(strPage) then anyPage = strURL break end end else anyPage = tblURL[1] or "" -- Default to first web page end oleControl.com:Navigate(tblHelp.RootURL..anyPage) -- Navigate to chosen web page end -- function HelpDialogue end -- function NewHelpDialogue function fh.AddHelpButton(strName,strTip,strURL,intPage) -- Add button to GUI Help Dialogue local tblHelp = tblNameFor("Help") if tblHelp and not strHelpButtonActive then for intHelp, tblHelp in ipairs(tblHelp) do -- Check button does not already exist if tblHelp.Name == strName then return end end if tonumber(intPage) then intPage = intPage + 1 end -- Optional external intPage number matches intTabPosn table.insert( tblHelp, { Name=strName or "?"; Tip=strTip or "?"; URL=strURL or ""; Page=intPage; } ) end end -- function AddHelpButton local function anyMemoControl(anyName,fgColor) -- Compose any control Title and FgColor local strName = tostring(anyName) -- anyName may be a string, and fgColor is default FgColor local tipText = nil if type(anyName) == "table" then -- anyName may be a table = { Title string ; FgColor string ; ToolTip string (optional); } strName = anyName[1] fgColor = anyName[2]:match("%d* %d* %d*") or fgColor tipText = anyName[3] end return strName, fgColor, tipText end -- local function anyMemoControl local function anyMemoDialogue(strHead,anyHead,strMemo,anyMemo,...) -- Display framed memo dialogue with buttons local arg = {...} -- Fix for Lua 5.2+ local intButt = 0 -- Returned value if "X Close" button is used local tblButt = { [0]="X Close"; } -- Button names lookup table local strHead, fgcHead, tipHead = anyMemoControl(anyHead or "",strHead) local strMemo, fgcMemo, tipMemo = anyMemoControl(anyMemo or "",strMemo) -- Create the GUI labels and buttons local lblMemo = iup.label { Title=strMemo; FgColor=fgcMemo; Tip=tipMemo; TipBalloon=fh.Balloon; Alignment="ACENTER"; Padding=fh.Margin; Expand="YES"; WordWrap="YES"; } local lblLine = iup.label { Separator="HORIZONTAL"; } local iupHbox = iup.hbox { Homogeneous="YES"; } local btnButt = iup.button { } local strTop = "YES" -- Make dialogue TopMost -- V3.6 local strMode = "popup" if arg[1] == "Keep Dialogue" then -- Keep dialogue open for a progress message strMode = "keep dialogue" lblLine = iup.label { } if not arg[2] then strTop = "NO" end -- User chooses TopMost -- V3.6 else if #arg == 0 then arg[1] = "OK" end -- If no buttons listed then default to an "OK" button for intArg, anyButt in ipairs(arg) do local strButt, fgcButt, tipButt = anyMemoControl(anyButt,fh.Safe) tblButt[intArg] = strButt btnButt = iup.button { Title=strButt; FgColor=fgcButt; Tip=tipButt; TipBalloon=fh.Balloon; Expand="NO"; MinSize="80"; Padding=fh.Margin; action=function() intButt=intArg return iup.CLOSE end; } iup.Append( iupHbox, btnButt ) end end -- Create dialogue and turn off resize, maximize, minimize, and menubox except Close button local iupMemo = iup.dialog { Title=fh.Plugin..fh.Version..strHead; TopMost=strTop; -- TopMost added -- V3.6 iup.vbox { Alignment="ACENTER"; Gap=fh.Gap; Margin=fh.Margin; iup.frame { Title=strHead; FgColor=fgcHead; Font=fh.FontHead; iup.vbox { Alignment="ACENTER"; Font=fh.FontBody; lblMemo; lblLine; iupHbox; }; }; }; } fh.ShowDialogue("Memo",iupMemo,btnButt,strMode) -- Show popup Memo dialogue window with righthand button in focus (if any) if strMode == "keep dialogue" then return lblMemo end -- Return label control so message can be changed iupMemo:destroy() return intButt, tblButt[intButt] -- Return button number & title that was pressed end -- local function anyMemoDialogue function fh.MemoDialogue(anyMemo,...) -- Multi-Button GUI like iup.Alarm and fhMessageBox, with "Memo" in frame return anyMemoDialogue(fh.Head,"Memo",fh.Body,anyMemo,...) end -- function MemoDialogue function fh.WarnDialogue(anyHead,anyMemo,...) -- Multi-Button GUI like iup.Alarm and fhMessageBox, with heading in frame return anyMemoDialogue(fh.Warn,anyHead,fh.Warn,anyMemo,...) end -- function WarnDialogue function fh.GetRegKey(strKey) -- Read Windows Registry Key Value local luaShell = luacom.CreateObject("WScript.Shell") local anyValue = nil if pcall( function() anyValue = luaShell:RegRead(strKey) end ) then return anyValue -- Return Key Value if found end return nil end -- function GetRegKey function fh.PutRegKey(strKey,anyValue,strType) -- Write Windows Registry Key Value local luaShell = luacom.CreateObject("WScript.Shell") local strAns = nil if pcall( function() strAns = luaShell:RegWrite(strKey,anyValue,strType) end ) then return true end return nil end -- function PutRegKey local function httpRequest(strRequest) -- Luacom http request protected by pcall() below local http = luacom.CreateObject("winhttp.winhttprequest.5.1") http:Open("GET",strRequest,false) http:Send() return http.Responsebody end -- local function httpRequest function fh.VersionInStore(anyPlugin) -- Obtain the Version in Plugin Store by Name or Id local strType = "name=" if type(anyPlugin) == "number" or tonumber(anyPlugin) then strType = "id=" end if anyPlugin then local strFile = fh.MachinePath.."\\VersionInStoreInternetError.dat" local strRequest ="http://www.family-historian.co.uk/lnk/checkpluginversion.php?"..strType..anyPlugin local isOK, strReturn = pcall(httpRequest,strRequest) if not isOK then -- Problem with Internet access local intTime = os.time() - 36000 -- Time in seconds 10 hours ago local tblAttr, strError = lfs.attributes(strFile) -- Obtain file attributes if not tblAttr or tblAttr.modification < intTime then -- File does not exist or was modified long ago fhMessageBox(strReturn.."\n The Internet appears to be inaccessible. ") end general.SaveStringToFile(strFile,strFile) -- Update file modified time return "0" end general.DeleteFile(strFile) -- Delete file if Internet is OK local strVersion = "0" if strReturn ~= nil then strVersion = strReturn:match("([%d%.]*),%d*") -- Version digits & dots then comma and Id digits end return strVersion or "0" else return "0" end end -- function VersionInStore local function intVersion(strVersion) -- Convert version string to comparable integer local intVersion = 0 local tblVersion = stringx.split(strVersion,".") for i=1,5 do intVersion = intVersion * 1000 + tonumber(tblVersion[i] or 0) end return intVersion end -- local function intVersion function fh.CheckVersionInStore() -- Check if later Version available in Plugin Store local strNewVer = fh.VersionInStore(fh.Plugin:gsub(" %- .*","")) local strOldVer = fh.Version if intVersion(strNewVer) > intVersion(strOldVer:match("%D*([%d%.]*)")) then fh.MemoDialogue("Later Version "..strNewVer.." of this Plugin is available from the Family Historian 'Plugin Store'.") end end -- function CheckVersionInStore function fh.PluginDataScope(strScope) -- Set default Plugin Data scope to per-Project, or per-User, or per-Machine strScope = tostring(strScope):lower() if strScope:match("mach") then -- Per-Machine strDefaultScope = "Machine" elseif strScope:match("user") then -- Per-User strDefaultScope = "User" end -- Per-Project is default end -- function PluginDataScope local function strToANSI(strFileName) if stringx.encoding() == "ANSI" then return strFileName end return fhConvertUTF8toANSI(strFileName) end -- local function strToANSI local function getPluginDataFileName(strScope) -- Get plugin data filename for chosen scope local isOK, strDataFile = pcall(fhGetPluginDataFileName,strScope) if not isOK then strDataFile = fhGetPluginDataFileName() end -- Before V5.0.8 parameter is disallowed and default = CURRENT_PROJECT return strToANSI(strDataFile) end -- local function getPluginDataFileName local function getDataFiles(strScope) -- Compose the Plugin Data file & path & root names local strPluginName = strToANSI(fh.Plugin) local strPluginPlain = stringx.plain(strPluginName) local strDataFile = getPluginDataFileName(strScope) -- Allow plugins with variant filenames to use same plugin data files strDataFile = strDataFile:gsub("\\"..strPluginPlain:gsub(" ","_"):lower(),"\\"..strPluginName) strDataFile = strDataFile:gsub("\\"..strPluginPlain..".+%.[D,d][A,a][T,t]$","\\"..strPluginName..".dat") if strDataFile == "" and strScope == "CURRENT_PROJECT" then -- Use standalone GEDCOM path & filename..".fh_data\Plugin Data\" as the folder + the Plugin Filename..".dat" strDataFile = strToANSI(fhGetContextInfo("CI_GEDCOM_FILE")) strDataFile = strDataFile:gsub("%.[G,g][E,e][D,d]",".fh_data") --# lfs.mkdir(strDataFile) general.MakeFolder(strDataFile) -- V3.4 strDataFile = strDataFile.."\\Plugin Data" --# lfs.mkdir(strDataFile) general.MakeFolder(strDataFile) -- V3.4 strDataFile = strDataFile.."\\"..strPluginName..".dat" end local strDataPath = strDataFile:gsub("\\"..strPluginPlain.."%.[D,d][A,a][T,t]$","") -- Plugin data folder path name local strDataRoot = strDataPath.."\\"..strPluginName -- Plugin data file root name general.MakeFolder(strDataPath) -- V3.4 return strDataFile, strDataPath, strDataRoot end -- local function getDataFiles function fh.Initialise(strVersion,strPlugin) -- Initialise the GUI module with optional Version & Plugin name local strAppData = strToANSI(fhGetContextInfo("CI_APP_DATA_FOLDER")) fh.Plugin = fhGetContextInfo("CI_PLUGIN_NAME") -- Plugin Name from file fh.Version = strVersion or " " -- Plugin Version if fh.Version == " " then local strTitle = "\n@Title is missing" local strAuthor = "\n@Author is missing" local strVersion = "\n@Version is missing" local strPlugin = strAppData.."\\Plugins\\"..fh.Plugin..".fh_lua" if general.FlgFileExists(strPlugin) then for strLine in io.lines(strPlugin) do -- Read each line from the Plugin file strPlugin = strLine:match("^@Title:[\t-\r ]*(.*)") if strPlugin then strPlugin = strPlugin:gsub("&&","&") --? if fh.Plugin:match("^"..strPlugin:gsub("(%W)","%%%1")) then if fh.Plugin:match("^"..stringx.plain(strPlugin)) then fh.Plugin = strPlugin -- Prefer Title to Filename if it matches strTitle = nil else strTitle = "\n@Title differs from Filename" -- Report abnormality end end if strLine:match("^@Author:%s*(.*)") then -- Check @Author exists strAuthor = nil end fh.Version = strLine:gsub("^@Version:%D*([%d%.]*)%D*"," %1 ") if fh.Version ~= strLine then -- Obtain the @Version from Plugin file strVersion = nil break end end if strTitle or strAuthor or strVersion then -- Report any header abnormalities fhMessageBox("\nScript Header: "..fh.Plugin..(strTitle or "")..(strAuthor or "")..(strVersion or "")) end else fhMessageBox("\nPlugin has not been saved!") end end fh.History = fh.Version -- Version History fh.Plugin = strPlugin or fh.Plugin -- Plugin Name from argument or default from file fh.CustomDialogue("Help","1020x730") -- Custom "Help" dialogue sizes fh.DefaultDialogue() -- Default "Font","Help","Main" dialogues fh.MachineFile,fh.MachinePath,fh.MachineRoot = getDataFiles("LOCAL_MACHINE") -- Plugin data names per machine fh.PerUserFile,fh.PerUserPath,fh.PerUserRoot = getDataFiles("CURRENT_USER") -- Plugin data names per user fh.ProjectFile,fh.ProjectPath,fh.ProjectRoot = getDataFiles("CURRENT_PROJECT") -- Plugin data names per project fh.FhDataPath = strToANSI(fhGetContextInfo("CI_PROJECT_DATA_FOLDER")) -- Paths used by Load/SaveFolder for relative folders fh.PublicPath = strToANSI(fhGetContextInfo("CI_PROJECT_PUBLIC_FOLDER")) -- Public data folder path name if fh.FhDataPath == "" then fh.FhDataPath = fh.ProjectPath:gsub("\\Plugin Data$","") end if fh.PublicPath == "" then fh.PublicPath = fh.ProjectPath fh.FhProjPath = fh.PublicPath:gsub("^(.+)\\.-\\Plugin Data$","%1") else general.MakeFolder(fh.PublicPath) -- V3.4 fh.FhProjPath = fh.PublicPath:gsub("^(.+)\\.-\\Public$","%1") end fh.CalicoPie = strAppData:gsub("\\Calico Pie\\.*","\\Calico Pie") -- Program Data Calico Pie path name fh.ComputerName = os.getenv("COMPUTERNAME") -- Local PC Computer Name end -- function Initialise fh.Initialise() -- Initialise module with default values return fh end -- local function iup_gui_v3 local iup_gui = iup_gui_v3() -- To access FH IUP GUI build module -- Preset Global Data Definitions -- function PresetGlobalData() -- Compose Date & Time Suffix for Log files -- local function strDateTime() local tblDate = {} tblDate = os.date("*t") return " - "..string.format("%04d",tblDate["year"]).."-"..string.format("%02d",tblDate["month"]).."-"..string.format("%02d",tblDate["day"]).." "..string.format("%02d",tblDate["hour"])..string.format("%02d",tblDate["min"])..string.format("%02d",tblDate["sec"]) end -- local function strDateTime local function strToUTF8(strFileName) if string.encoding() == "ANSI" then return strFileName end return fhConvertANSItoUTF8(strFileName) end -- local function strToUTF8 -- GUI Global Constants iup_gui.Gap = "4" iup_gui.Margin = "3x1" iup_gui.Balloon = "NO" -- Needed for PlayOnLinux/Mac -- V3.1 -- Tip Balloon popup 13 Nov 2013 iup_gui.SetUtf8Mode() AnsiPluginTitle = encoder.StrEncode_ANSI(iup_gui.Plugin) AnsiProjectPath = iup_gui.ProjectPath AnsiLogFilePath = AnsiProjectPath.."\\"..AnsiPluginTitle..strDateTime()..".log" Utf8LogFilePath = strToUTF8(AnsiLogFilePath) Utf8ProjectPath = strToUTF8(AnsiProjectPath) Utf8PluginTitle = iup_gui.Plugin IntMaxNameLen = 22 -- Length at which Button label Tag Names are truncated LogFileHandle = nil StrLogNeedTitle = "Log Edits to File" -- Radio button titles StrPatternTitle = "Pattern Filters" StrConfirmTitle = "Confirm all Edits" StrWarningTitle = "Show all Warnings" StrOldData = "" -- Description of Source Tag e.g. Birth, Occupation, etc StrNewData = "" -- Description of Target Tag -- Statistics for Tags processed IntSkippedData = 0 -- Count of current skipped Tags IntDeletedData = 0 -- Count of current deleted Tags IntChangedData = 0 -- Count of current changed Tags IntWarningData = 0 -- Count of current warning Messages IntSkippedTotal = 0 -- Count of total skipped Tags IntDeletedTotal = 0 -- Count of total deleted Tags IntChangedTotal = 0 -- Count of total changed Tags IntWarningTotal = 0 -- Count of total warnings -- Global constants StrDeleteTag = "Delete" -- New Tag value to signal delete Old Tag StrSkipTag = "Skip" -- New Tag value to signal skip Old Tag StrModeOld = "Old" -- Parameter value to select only Old Tag StrModeNew = "New" -- Parameter value to select only New Tag StrModeAll = "All" -- Parameter value to select both Old & New Tag IntHelpCheck = 1 -- Parameter value to select Warning GUI Check Tags compatible message IntHelpNoted = 2 -- Parameter value to select Warning GUI Value in Source Note message IntHelpReport = 3 -- Parameter value to select Warning GUI Report to FHUG Forums message IntFilterValue = 1 -- Filter index for Tag selection Value Filter IntFilterDate = 2 -- Filter index for Tag selection Date Filter IntFilterPlace = 3 -- Filter index for Tag selection Place Filter IntFilterRecord = 4 -- Filter index for Records selection Filter -- V2.9 -- Predefined Tag Set Name constants for lookup tables StrStandard = "Standard" -- Standard Fact Set name, file & folder StrCustomEvents = "" -- Dummy Undefined Custom Events Set -- FH V7 -- V3.1 StrCustomAttrs = "" -- Dummy Undefined Custom Attributes Set -- FH V7 -- V3.1 StrDefinedTags = "" -- Dummy Gedcom 5.5 Defined Tags Set StrUDFTagsSet = "" -- Dummy Uncategorised Data Field (UDF) Tags Set TblRecordTag = {"INDI";"FAM";"NOTE";"SOUR";"REPO";"OBJE";"_PLAC";"SUBM";"SUBN";"_RNOT";"_SRCT";"HEAD";} TblRecordName = {"Individual";"Family";"Note";"Source";"Repository";"Multimedia";"Place";"Submitter";"Submission";"Resource Note";"Source Template";"Header";} -- Tag code & name table variables TblOldTagIndex = {} -- TblOldTagIndex[strRecTag][strTagSet][intItem] = strOldTag and TblOldTagIndex[strRecTag][strTagSet][strOldTag] = true for Source Name dropdown lists TblNewTagIndex = {} -- TblNewTagIndex[strRecTag][strTagSet][intItem] = strNewTag and TblNewTagIndex[strRecTag][strTagSet][strOldTag] = true for Target Name dropdown lists TblSetNameIndex = {} -- TblSetNameIndex[strRecTag][intItem] = strSetName for Source/Target Set dropdown lists TblDictionary = {} -- TblDictionary[strRecTag][strTag] = strName and TblDictionary[strRecTag][strTagSet][strName] = strTag both way translations for intRecTag,strRecTag in ipairs(TblRecordTag) do TblOldTagIndex [strRecTag] = {} TblNewTagIndex [strRecTag] = {} TblSetNameIndex[strRecTag] = {} TblDictionary [strRecTag] = {} for intItem, strSetName in ipairs({StrCustomAttrs;StrCustomEvents;StrDefinedTags;StrUDFTagsSet;}) do -- FH V7 -- V3.1 TblOldTagIndex[strRecTag][strSetName] = {} TblNewTagIndex[strRecTag][strSetName] = {} TblDictionary [strRecTag][strSetName] = {} -- V2.5 end -- Special RecordName v RecordTag both way translations TblDictionary[TblRecordName[intRecTag]] = strRecTag TblDictionary[strRecTag]["%"] = TblRecordName[intRecTag] end end -- function PresetGlobalConstants -- Reset Sticky Settings to Default Values -- function ResetDefaultSettings() iup_gui.CustomDialogue("Main","0x0") -- Custom "Main" dialogue minimum size & centralisation iup_gui.CustomDialogue("Font","0x0") -- Custom "Font" dialogue minimum size & centralisation iup_gui.CustomDialogue("Memo","0x0") -- Custom "Memo" dialogue minimum size & centralisation iup_gui.CustomDialogue("Logs","0x0") -- Custom "Logs" dialogue minimum size & centralisation iup_gui.DefaultDialogue("Bars","Memo","Logs") -- GUI window position X & Y co-ordinates, and minsize & rastersize for "Main","Font","Bars","Memo","Logs" dialogues StrLogNeed = "ON" -- Radio button "ON"/"OFF" states StrPattern = "ON" StrConfirm = "ON" StrWarning = "ON" end -- function ResetDefaultSettings -- Load Sticky Settings from File -- function LoadSettings() iup_gui.LoadSettings("Bars","Memo","Logs") -- Includes "Main","Font","Bars","Memo","Logs" dialogues and "FontSet" & "History" StrLogNeed = tostring(iup_gui.LoadGlobal("LogNeed",StrLogNeed)) StrPattern = tostring(iup_gui.LoadGlobal("RegExpr",StrPattern)) -- Legacy filter option StrPattern = tostring(iup_gui.LoadGlobal("Pattern",StrPattern)) StrConfirm = tostring(iup_gui.LoadGlobal("Confirm",StrConfirm)) StrWarning = tostring(iup_gui.LoadGlobal("Warning",StrWarning)) SaveSettings() -- Save sticky data settings end -- function LoadSettings -- Save Sticky Settings to File -- function SaveSettings() iup_gui.SaveGlobal("LogNeed",StrLogNeed) iup_gui.SaveGlobal("Pattern",StrPattern) iup_gui.SaveGlobal("Confirm",StrConfirm) iup_gui.SaveGlobal("Warning",StrWarning) iup_gui.SaveSettings("Bars","Memo","Logs") -- Includes "Main","Font","Bars","Memo","Logs" dialogues and "FontSet" & "History" end -- function SaveSettings -- Determine if a Table has Data -- function FlgTableHasData(tblTable) if next(tblTable) == nil then return false else return true end end -- function FlgTableHasData -- Insert a Tag in Old &/or New Tag Index of Tags defined by Tag Set function DoInsertTagIndex(strRecTag,strSet,strTag,strMode) -- strMode = StrModeOld to only update Old Tag Index -- strMode = StrModeNew to only update New Tag Index -- strMode = StrModeAll to update both Old & New Index if strMode == StrModeOld or strMode == StrModeAll then local tblOldTagIndex = TblOldTagIndex[strRecTag][strSet] -- V2.5 if not tblOldTagIndex[strTag] then -- V2.5 tblOldTagIndex[strTag] = true table.insert(tblOldTagIndex,strTag) -- Add Tag to Old Tag list end end if strMode == StrModeNew or strMode == StrModeAll then local tblNewTagIndex = TblNewTagIndex[strRecTag][strSet] -- V2.5 if not tblNewTagIndex[strTag] then -- V2.5 tblNewTagIndex[strTag] = true table.insert(tblNewTagIndex,strTag) -- Add Tag to New Tag list end end end -- function DoInsertTagIndex -- Remove a Tag from Old Tag Index of Tags defined by Tag Set function DoRemoveTagIndex(strRecTag,strOldSet,strOldTag) local tblOldTagIndex = TblOldTagIndex[strRecTag][strOldSet] -- V2.5 if tblOldTagIndex[strOldTag] then -- V2.5 for intTag,strTag in ipairs(tblOldTagIndex) do if strTag == strOldTag then table.remove(tblOldTagIndex,intTag) tblOldTagIndex[strOldTag] = nil break end end end end -- function DoRemoveTagIndex -- Load Standard & Custom & UDF Facts & Tags -- function LoadFactsAndTags() -- Load a Facts File into Facts Tables local function doLoadFactsTable(dicFactSet) -- V3.0 local strFactsFile = dicFactSet.File -- V3.0 local strFactsName = dicFactSet.Name -- V3.0 local strRecTag, strOldTag, strFact -- V2.6 -- Parse a Fact Item from a Fact File local function strstrParseFactItem(strItem) -- Standard Facts have format such as TAG-IE or TAG-FA -- Custom Facts have format such as EVEN-TAG-IE or _ATTR-TAG-FA or FACT-TAG-FA -- FH V7 -- V3.1 -- Witness Roles have format such as TAG-IE-ROLE or TAG-FA-ROLE to be ignored local strTag,strEnd,strRole = strItem:match("^(.+)%-([IF])[EA]([%-ROLE]-)$") -- V2.7 if strRole == "-ROLE" then -- Ignore Witness Roles -- V2.7 return nil,nil elseif strEnd == "I" then -- "IA" Indiv Attr or "IE" Indiv Event return strTag,"INDI" elseif strEnd == "F" then -- "FA" Family Attr or "FA" Family Event return strTag,"FAM" else error("\n\n Unrecognised fact item "..strItem.." \n in "..strFactsFile.." \n\n") end end -- local function strstrParseFactItem TblOldTagIndex["INDI"][strFactsName] = {} TblNewTagIndex["INDI"][strFactsName] = {} TblDictionary ["INDI"][strFactsName] = {} -- V2.5 TblOldTagIndex["FAM"] [strFactsName] = {} TblNewTagIndex["FAM"] [strFactsName] = {} TblDictionary ["FAM"] [strFactsName] = {} -- V2.5 for strLine in encoder.FileLines(strFactsFile,"UTF-8") do -- V3.0 local strPref, strSuff = strLine:match("^([%[%a]-)%d-[=%-](.-)%]?$") -- V2.6 if strPref and strSuff then if strPref:match("^%[") and strRecTag and strOldTag and strFact then -- V2.8 DoInsertTagIndex(strRecTag,strFactsName,strOldTag,StrModeNew) TblDictionary[strRecTag][strOldTag] = strFact -- Save the Fact Tag details strRecTag = nil strOldTag = nil strFact = nil end if strPref == "Item" -- Found a Fact Tag item such as Item21=EVEN-TAG-IE or Item=TAG-FA or strPref == "[FCT" then -- Found start of Fact definition such as [FCT-TAG-IE] strOldTag,strRecTag = strstrParseFactItem(strSuff) strFact = "" -- V2.5 elseif strPref == "Name" then -- Found Fact Name to save against Tag strFact = strSuff -- V2.5 elseif strPref == "Label" then -- Found Fact Label preferred to Fact Name if #strFact > IntMaxNameLen or #strSuff <= IntMaxNameLen then strFact = strSuff -- V2.5 end elseif strPref == "Abbr" and #strSuff > 0 then -- Found Fact Abbr to replace long Fact Name -- V3.0 if #strFact > IntMaxNameLen and #strSuff <= IntMaxNameLen then strFact = strSuff -- V2.6 end elseif strPref == "Hidden" and strSuff == "Y" then strFact = strFact.." " -- Fact Name is -- V2.8 end end end for intItem, strRecTag in ipairs({"INDI";"FAM";}) do if strFactsName ~= StrStandard then table.insert(TblSetNameIndex[strRecTag],2,strFactsName) -- Reverse order of Fact Sets, but with Standard always top -- V2.6 end for intOldTag,strOldTag in ipairs(TblNewTagIndex[strRecTag][strFactsName]) do local strName = TblDictionary[strRecTag][strOldTag] TblDictionary[strRecTag][strFactsName][strName] = strOldTag -- V2.5 end end end -- local function doLoadFactsTable -- Load Gedcom Defined Tags to Dummy Tag Set local function doLoadDefinedTags(strRecTag,tblRecTag) for strOldTag,strOldName in pairs(tblRecTag) do DoInsertTagIndex(strRecTag,StrDefinedTags,strOldTag,StrModeNew) TblDictionary[strRecTag][strOldTag] = strOldName TblDictionary[strRecTag][StrDefinedTags][strOldName] = strOldTag -- V2.5 end end -- local function doLoadDefinedTags -- Load Undefined Tags to Dummy Tag Sets local function doLoadUndefinedTags() local strSuffix = "" local flgTagFound = false local strOldTag = "" local ptrOldTag = fhNewItemPtr() local ptrRecord = fhNewItemPtr() for intRecTag,strRecTag in ipairs(TblRecordTag) do ptrRecord:MoveToFirstRecord(strRecTag) while not ptrRecord:IsNull() do -- Search each Record ptrOldTag:MoveToFirstChildItem(ptrRecord) while not ptrOldTag:IsNull() do -- Search each Tag strOldTag = fhGetTag(ptrOldTag) if fhIsEvent(ptrOldTag) or fhIsAttribute(ptrOldTag) then -- Found an Event/Attribute Fact for intSet,strSet in ipairs(TblSetNameIndex[strRecTag]) do -- Search existing Fact Sets and Fact Tags flgTagFound = false if TblNewTagIndex[strRecTag][strSet][strOldTag] then -- V2.5 DoInsertTagIndex(strRecTag,strSet,strOldTag,StrModeOld) -- Add current Fact Tag to Old Tag index for Fact Set flgTagFound = true break -- Escape if Fact Tag is found end end if not flgTagFound then -- Add undefined Tag to dummy undefined Custom Events/Attributes fact set -- V3.1 local strOldDisp = fhGetDisplayText(ptrOldTag) .. " " -- Extract undefined fact name -- V3.1 local _, intLast = strOldDisp:find("^(.-) ",#strOldTag-5) -- #strOldTag:match("^.-%-(.+)$")) local strOldName = strOldDisp:sub(1,intLast-1) local strCustom = StrCustomEvents if fhIsAttribute(ptrOldTag) then strCustom = StrCustomAttrs end DoInsertTagIndex(strRecTag,strCustom,strOldTag,StrModeAll) TblDictionary[strRecTag][strOldTag] = strOldName TblDictionary[strRecTag][strCustom][strOldName] = strOldTag -- V2.5 end elseif fhIsUDF(ptrOldTag) then -- Found UDF Tag strSuffix = " UDF Tag" if strRecTag == "INDI" or strRecTag == "FAM" then local strOldVal = fhGetValueAsText(ptrOldTag) local ptrLink,strTag = PtrStrTranslateLink(strOldVal) -- If value is a link such as @S99@ then not valid as a Fact if ptrLink == nil then strSuffix = " UDF Attribute" if strOldVal == "" and TblNewTagIndex[strRecTag][StrUDFTagsSet][strOldTag] ~= strOldTag..strSuffix then strSuffix = " UDF Event" -- If its value is null, and not already an Attribute, then it is an Event end end end DoInsertTagIndex(strRecTag,StrUDFTagsSet,strOldTag,StrModeAll) -- Add uncategorised Data Tag to dummy UDF Tag Set TblDictionary[strRecTag][strOldTag] = strOldTag..strSuffix TblDictionary[strRecTag][StrUDFTagsSet][strOldTag..strSuffix] = strOldTag -- V2.5 else DoInsertTagIndex(strRecTag,StrDefinedTags,strOldTag,StrModeOld) -- Add current defined Data Tag to Old Tag index if TblDictionary[strRecTag][strOldTag] == nil then TblDictionary[strRecTag][strOldTag] = strOldTag.." not Defined !" -- Fallback in case Tag is not in Set TblDictionary[strRecTag][StrDefinedTags][strOldTag.." not Defined !"] = strOldTag -- V2.5 end end ptrOldTag:MoveNext("ANY") end ptrRecord:MoveNext("SAME_TAG") -- Move to next Record end for intItem,strSetName in ipairs({StrCustomEvents;StrCustomAttrs;StrDefinedTags;StrUDFTagsSet;}) do if FlgTableHasData(TblOldTagIndex[strRecTag][strSetName]) then table.insert(TblSetNameIndex[strRecTag], strSetName) end end end end -- local function doLoadUndefinedTags() table.insert(TblSetNameIndex["INDI"], 1, StrStandard) -- Ensure Standard is always top of list table.insert(TblSetNameIndex["FAM"] , 1, StrStandard) -- Use the GroupIndex to process Fact Sets in reverse order to obscure Facts (like Export Gedcom File) -- V2.6 -- V3.0 local dicGroupIndex = {} local strDataFolder = fhGetContextInfo("CI_PROJECT_DATA_FOLDER").."\\Fact Types" local strGroupIndex = strDataFolder.."\\GroupIndex.fhdata" if general.FlgFileExists(strGroupIndex) then for strLine in encoder.FileLines(strGroupIndex,"ANSI") do -- Read each line in Project only GroupIndex -- V3.2 local strFactSet, intFactSet = strLine:match("^(.+)=(%d+)$") -- Extract Fact Set name and ordinal position intFactSet = tonumber(intFactSet) if strFactSet and intFactSet and not dicGroupIndex[intFactSet] then -- Compose Fact Set filename local strFactsFile = strDataFolder.."\\"..strFactSet..".fhf" if general.FlgFileExists(strFactsFile) then -- Fact Set name and file exist, so save in ordinal order dicGroupIndex[intFactSet] = { Name=strFactSet.." (project)"; File=strFactsFile; } end end end end local intGroupIndex = #dicGroupIndex local strDataFolder = fhGetContextInfo("CI_APP_DATA_FOLDER").."\\Fact Types" local strGroupIndex = strDataFolder.."\\Standard\\GroupIndex.fhdata" if general.FlgFileExists(strGroupIndex) then for strLine in encoder.FileLines(strGroupIndex,"ANSI") do -- Read each line in ProgramData GroupIndex -- V3.0 local strFactSet, intFactSet = strLine:match("^(.+)=(%d+)$") -- Extract Fact Set name and ordinal position intFactSet = tonumber(intFactSet) if strFactSet and intFactSet and not dicGroupIndex[intFactSet+intGroupIndex] then -- Compose Fact Set filename -- V3.0 local strFactsFile = strDataFolder.."\\Custom\\"..strFactSet..".fhf" if strFactSet == StrStandard then strFactsFile = strDataFolder.."\\Standard\\Standard.fhf" end if general.FlgFileExists(strFactsFile) then -- Fact Set name and file exist, so save in ordinal order -- V3.0 dicGroupIndex[intFactSet+intGroupIndex] = { Name=strFactSet; File=strFactsFile; } end end end end for intFactSet = #dicGroupIndex, 1, -1 do -- Reverse order of Fact Sets doLoadFactsTable(dicGroupIndex[intFactSet]) -- Import Fact Set file to Fact tables -- V3.0 end -- Load common GEDCOM 5.5 1996 & Place Defined Tag Sets doLoadDefinedTags("INDI" ,{RIN="Automated Record Id";ALIA="Alias";ANCI="Ancestor Interest";ASSO="Associated Person";REFN="Custom Id";DESI="Descendant Interest";_FLGS="Flags";CHAN="Last Change Notes";AFN="LDS Ancestral File No.";BAPL="LDS Baptism";SLGC="LDS Child Sealing";CONL="LDS Confirmation";ENDL="LDS Endowment";OBJE="Multimedia Object";OBJE2="Multimedia Object (local)";NAME="Name";NOTE="Note Record";NOTE2="Note";RFN="Permanent Record No.";FAMC="Parents Family";FAMS="Spouse Family";RESN="Restriction Notice";SEX="Sex";SOUR="Source (citation)";SOUR2="Source Note";SUBM="Submitter";}) doLoadDefinedTags("FAM" ,{RIN="Automated Record Id";CHIL="Child";NCHI="Child Count";REFN="Custom Id";HUSB="Husband";CHAN="Last Change Notes";SLGS="LDS Spouse Sealing";OBJE="Multimedia Object";OBJE2="Multimedia Object (local)";_STAT="Marriage Status";NOTE="Note Record";NOTE2="Note";SOUR="Source (citation)";SOUR2="Source Note";SUBM="Submitter";WIFE="Wife";}) doLoadDefinedTags("NOTE" ,{RIN="Automated Record Id";TEXT="Text";REFN="Custom Id";CHAN="Last Change Notes";SOUR="Source (citation)";SOUR2="Source Note";}) doLoadDefinedTags("SOUR" ,{RIN="Automated Record Id";AUTH="Author";REFN="Custom Id";DATA="Data";CHAN="Last Change Notes";NOTE="Note Record";NOTE2="Note";PUBL="Publication Info";OBJE="Multimedia Object";OBJE2="Multimedia Object (local)";REPO="Repository";ABBR="Short Title";TEXT="Text From Source";TITL="Title";_TYPE="Type";}) doLoadDefinedTags("REPO" ,{RIN="Automated Record Id";ADDR="Address";REFN="Custom Id";CHAN="Last Change Notes";NAME="Name";NOTE="Note Record";NOTE2="Note";PHON="Phone Number";}) doLoadDefinedTags("OBJE" ,{RIN="Automated Record Id";REFN="Custom Id";_DATE="Date";_KEYS="Keywords";CHAN="Last Change Notes";NOTE="Note Record";}) doLoadDefinedTags("_PLAC",{CHAN="Last Change Notes";LATLONG="Lat./Longitude";OBJE="Multimedia Object";NOTE2="Note";TEXT="Place Name";STAN="Standardized";STAT="Status";}) doLoadDefinedTags("SUBM" ,{RIN="Automated Record Id";ADDR="Address";NAME="Name";LANG="Language";CHAN="Last Change Notes";OBJE="Multimedia Object";OBJE2="Multimedia Object (local)";PHON="Phone Number";RFN="Permanent Record No.";}) doLoadDefinedTags("SUBN" ,{RIN="Automated Record Id";ANCE="Ancestor Generations";DESC="Descendant Generations";FAMF="Family File Name";CHAN="Last Change Notes";ORDI="Ordinance Process";SUBM="Submitter";TEMP="Temple Code";}) doLoadDefinedTags("HEAD" ,{CHAR="Character Set";COPR="Copyright";DATE="Transmission Date";DEST="Receiving System";FILE="Filename";_ROOT="File Root";GEDC="GEDCOM";LANG="Language";NOTE2="File Description";PLAC="Place Format";_ROOT="File Root";_USED="Last-used Record Id";SOUR2="System Id";SUBM="Submitter";SUBN="Submission";_UID="User Id";}) if fhGetAppVersion() < 7 then -- Load variants for GEDCOM 5.5 1996 Defined Tag Sets -- FH V7 -- V3.1 doLoadDefinedTags("REPO" ,{_EMAIL="Email";_WEB="Web Site";}) doLoadDefinedTags("OBJE" ,{BLOB="Bulk Object";_FILE="File";FORM="Format";_NOTE="Note";NOTE2="Link/Note";TITL="Title";}) -- OBJE="Multimedia Link" associated only with BLOB continuation doLoadDefinedTags("SUBM" ,{_EMAIL="Email";_WEB="Web Site";}) else -- Load variants for GEDCOM 5.5.1 2019 & Research Note & Source Template Defined Tag Sets -- FH V7 -- V3.1 doLoadDefinedTags("INDI" ,{_RNOT="Research Note";}) -- ???="Family Search Id";}) doLoadDefinedTags("FAM" ,{_RNOT="Research Note";}) doLoadDefinedTags("SOUR" ,{_RNOT="Research Note";_SRCT="Source Template";}) doLoadDefinedTags("REPO" ,{_RNOT="Research Note";EMAIL="Email";WWW="Web Site";}) doLoadDefinedTags("_PLAC",{_RNOT="Research Note";}) doLoadDefinedTags("OBJE" ,{_RNOT="Research Note";_NOTA="Annotation";FILE="File";NOTE2="Note";}) doLoadDefinedTags("SUBM" ,{EMAIL="Email";WWW="Web Site";}) doLoadDefinedTags("_RNOT",{REFN="Custom Id";CHAN="Last Change Notes";TEXT="Text";}) doLoadDefinedTags("_SRCT",{BIBL="Bibliography";CATG="Category";COLL="Collection";DESC="Description";FDEF="Field Definition";FOOT="Footnote";CHAN="Last Change Notes";NAME="Name";NOTE2="Note";TITL="Record Title";REFN="Reference";SHRT="Short Footnote";SUBC="Subcategory";}) end -- Load any undefined Custom Fact or Uncategorised Data Field (UDF) into dummy Tag Sets doLoadUndefinedTags() end -- function LoadFactsAndTags -- Write Line to Log File -- function WriteLogFile(strLine) if StrLogNeed == "ON" then -- Log file enabled LogFileHandle = general.OpenFile(AnsiLogFilePath,"ab") local strBOM = "" if string.encoding() == "UTF-8" then strBOM = string.char(0xEF,0xBB,0xBF) -- UTF-8 BOM end if LblLogFile.Title ~= Utf8LogFilePath then -- Log file has just been created LblLogFile.Title = Utf8LogFilePath -- Report log file name in Main GUI LogFileHandle:write(strBOM.."Folder\t"..Utf8ProjectPath.."\r\n")-- Insert folder & name details into log file LogFileHandle:write("Logfile\t"..Utf8LogFilePath:gsub("^"..Utf8ProjectPath.."\\","").."\r\n\r\n") end LogFileHandle:write(strLine) -- Write data line to log file LogFileHandle:close() end end -- function WriteLogFile -- Update Count Status -- function UpdateCountStatus() local function strCountFormat(intCount,strFormat) if strFormat == "Warnings." and intCount == 1 then strFormat = "Warning. " end strFormat = string.format("%4d ",intCount)..strFormat return strFormat end -- local function strCountFormat local strCounts = "" -- Counts log report local strTotals = "" -- Totals log report local strFormat = "" -- Formatted data -- Report Data Counts strFormat = strCountFormat(IntSkippedData,"Skipped.") LblCoSkipped.Title = strFormat strCounts = strCounts..strFormat.."\t" strFormat = strCountFormat(IntDeletedData,"Deleted.") LblCoDeleted.Title = strFormat strCounts = strCounts..strFormat.."\t" strFormat = strCountFormat(IntChangedData,"Changed.") LblCoChanged.Title = strFormat strCounts = strCounts..strFormat.."\t" strFormat = strCountFormat(IntWarningData,"Warnings.") LblCoWarning.Title = strFormat strCounts = strCounts..strFormat.."\r\n" -- Report Total Counts strFormat = strCountFormat(IntSkippedTotal,"Skipped.") LblToSkipped.Title = strFormat strTotals = strTotals..strFormat.."\t" strFormat = strCountFormat(IntDeletedTotal,"Deleted.") LblToDeleted.Title = strFormat strTotals = strTotals..strFormat.."\t" strFormat = strCountFormat(IntChangedTotal,"Changed.") LblToChanged.Title = strFormat strTotals = strTotals..strFormat.."\t" strFormat = strCountFormat(IntWarningTotal,"Warnings.") LblToWarning.Title = strFormat strTotals = strTotals..strFormat.."\r\n" WriteLogFile("Counts\t"..strCounts) --? WriteLogFile("Totals\t"..strTotals) WriteLogFile("\r\n") return strCounts:gsub("\t"," ") end -- function UpdateCountStatus -- Get Display of Record Name -- function StrDisplayName(ptrName) return fhGetDisplayText(ptrName).." ["..fhGetRecordId(ptrName).."]" end -- function StrDisplayName -- Get Display of Record Data -- function StrDisplayData(ptrData,strData) return general.BuildDataRef(ptrData)..": "..fhGetDisplayText(ptrData) -- V2.5 end -- function StrDisplayData -- Global tables for Query Result Set Columns TblPtrName = {} TblOldItem = {} TblOldData = {} TblPerform = {} TblNewItem = {} TblPtrData = {} -- Write Tag Change to Log File and Increment Counts -- function WriteDataChange(ptrName,ptrOld,ptrNew,strNew) local strLine strLine = "Name\t'"..TblDictionary[fhGetTag(ptrName)]["%"]..": "..StrDisplayName(ptrName).."'\r\n" strLine = strLine.."Data\t'"..StrDisplayData(ptrOld,StrOldData) table.insert(TblPtrName,ptrName:Clone()) table.insert(TblOldItem,StrOldData) table.insert(TblOldData,StrDisplayData(ptrOld,StrOldData)) table.insert(TblPerform,"unknown") table.insert(TblNewItem,"") table.insert(TblPtrData,"") if strNew == StrDeleteTag then IntDeletedData = IntDeletedData + 1 IntDeletedTotal = IntDeletedTotal + 1 strLine = strLine.."' deleted.\r\n" TblPerform[#TblPtrName] = "deleted" elseif strNew == StrSkipTag then IntSkippedData = IntSkippedData + 1 IntSkippedTotal = IntSkippedTotal + 1 strLine = strLine.."' skipped.\r\n" TblPerform[#TblPtrName] = "skipped" TblPtrData[#TblPtrName] = ptrOld:Clone() else IntChangedData = IntChangedData + 1 IntChangedTotal = IntChangedTotal + 1 strLine = strLine.."' became '"..StrDisplayData(ptrNew,StrNewData).."'\r\n" TblPerform[#TblPtrName] = "changed" TblNewItem[#TblPtrName] = StrNewData TblPtrData[#TblPtrName] = ptrNew:Clone() end WriteLogFile(strLine) end -- function WriteDataChange -- Translate NOTE/OBJE/SOUR link @@N99@@ or @O99@ or @@S99@@ into a Pointer and Tag -- function PtrStrTranslateLink(strLink) local ptrLink = fhNewItemPtr() -- return Null pointer if no link found local strTag = nil if strLink:sub(1,1) == "@" and strLink:sub(-1) == "@" then strLink = strLink:gsub("@","") -- Starts & ends with @ so looks like a link that needs every @ removed local tblTag = { N="NOTE"; O="OBJE"; S="SOUR"; } strTag = tblTag[strLink:sub(1,1)] -- Check and convert initial letter N or O or S to Tag if strTag then -- V2.5 strLink = strLink:sub(2) -- Remove initial letter to leave number ID ptrLink = fhNewItemPtr() ptrLink:MoveToRecordById(strTag,tonumber(strLink)) -- Obtain link to record using number ID end end return ptrLink,strTag end -- function PtrStrTranslateLink -- Edit the Data Details for Source Tag and Target Tag -- function DoEditDataDetails(ptrRecord,ptrOld,strNew) local ptrNew = fhNewItemPtr() local tblLog = {} -- Log of warnings for SOURce Notes local intLog = 0 -- Log count for warnings local tblTag = {} -- Tag data ref hierarchy local intTag = 1 -- Tag level for hierarchy tblTag[intTag] = fhGetTag(ptrRecord) -- "INDI","FAM","NOTE","SOUR",etc -- Compose Current Data Reference -- local function strDataReference(strTag) local strDataRef = "%" for intItem, strName in ipairs(tblTag) do strDataRef = strDataRef..strName.."." end strDataRef = strDataRef..strTag.."%" return strDataRef end -- local function strDataReference -- Log and Report Warning Message -- local function doReportWarning(strTag,strVal,strWarn,intHelp) -- Compose new Data Reference & Value & Warning message strWarn = strDataReference(strTag).." "..strVal.." ~ "..strWarn intLog = intLog + 1 tblLog[intLog] = strWarn IntWarningData = IntWarningData + 1 IntWarningTotal = IntWarningTotal + 1 if StrWarning == "ON" then GUI_WarnDialogue(StrDisplayName(ptrRecord),StrDisplayData(ptrOld,StrOldData),strWarn,intHelp) end end -- local function doReportWarning -- Copy All Children Items -- local function doCopyChildrenItems(ptrOld,ptrNew) -- Copy All Child Branch Items -- local function doCopyChildBranch(ptrOrigin,ptrTarget) local strTag = fhGetTag(ptrOrigin) local strVal = fhGetValueAsText(ptrOrigin) --? local lnkVal = fhGetValueAsLink(ptrOrigin) -- Diagnostic --? local iValue = fhGetValueAsInteger(ptrOrigin) -- Diagnostic --? local dtDate = fhGetValueAsDate(ptrOrigin) -- Diagnostic --? local strTxt = fhGetItemText(ptrOrigin,strTag) -- Diagnostic --? local strType = fhGetValueType(ptrOrigin) -- Diagnostic --? local strClas = fhGetDataClass(ptrOrigin) -- Diagnostic local ptrNew = fhNewItemPtr() local function doConvertUDF_Link() local ptrLink,strLink = PtrStrTranslateLink(strVal) -- Convert UDF SOUR/OBJE/NOTE Link to Pointer & Tag if ptrLink:IsNotNull() then ptrNew = fhCreateItem(strLink,ptrTarget) if ptrNew:IsNull() then doReportWarning(strLink,strVal,"Invalid Tag",IntHelpCheck) else if not fhSetValueAsLink(ptrNew,ptrLink) then -- Set Link Pointer as new Tag value doReportWarning(strLink,strVal,"Link Pointer value not accepted",IntHelpCheck) end doCopyChildrenItems(ptrOrigin,ptrNew) end return nil end strTag = strTag.."2" -- Convert UDF SOUR/OBJE/NOTE to text Tag return strTag end -- local function doConvertUDF_Link local function doConvertUDF_Date() ptrNew = fhCreateItem(strTag,ptrTarget) if ptrNew:IsNull() then doReportWarning(strTag,strVal,"Invalid Tag",IntHelpCheck) else local dtDate = fhNewDate(1999) -- Convert Date string to Date Object if not dtDate:SetValueAsText(strVal,false) then doReportWarning(strTag,strVal,"Unrecognised 'Date' value, treated as 'Date Phrase'",IntHelpNoted) if not dtDate:SetValueAsText(strVal,true) then doReportWarning(strTag,strVal,"Unrecognised 'Date Phrase' value",IntHelpNoted) else if not fhSetValueAsDate(ptrNew,dtDate) then -- Set Date Object as new Tag value doReportWarning(strTag,strVal,"Date Object value not accepted",IntHelpCheck) end end else if not fhSetValueAsDate(ptrNew,dtDate) then -- Set Date Object as new Tag value doReportWarning(strTag,strVal,"Date Object value not accepted",IntHelpCheck) end end doCopyChildrenItems(ptrOrigin,ptrNew) end return nil end -- local function doConvertUDF_Date local function doConvertUDF_Even() ptrNew = fhCreateItem(strTag,ptrTarget) if ptrNew:IsNull() then doReportWarning(strTag,strVal,"Invalid Tag",IntHelpCheck) else local tblEvent = {} -- EVENt can be EVEN, ADOP, BIRT, BAPM, CAST, etc, etc local strEvent = nil tblEvent[1] = { EVEN="Event"; CAST="Social Rank"; CHRA="Adult Christening"; } -- Exceptions not translated below tblEvent[2] = TblDictionary["INDI"] -- All the Individual Fact tag translations tblEvent[3] = TblDictionary["FAM"] -- All the Family Fact tag translations for i=1,3 do strEvent = tblEvent[i][strVal] if strEvent then break end -- V2.5 end if strEvent == nil then doReportWarning(strTag,strVal,"Unrecognised 'Event Type' value",IntHelpReport) else if not fhSetValueAsText(ptrNew,strEvent) then doReportWarning(strTag,strEvent,"Event Type text not accepted",IntHelpCheck) end end doCopyChildrenItems(ptrOrigin,ptrNew) end return nil end -- local function doConvertUDF_Even local function doConvertUDF_Quay() ptrNew = fhCreateItem(strTag,ptrTarget) if ptrNew:IsNull() then doReportWarning(strTag,strVal,"Invalid Tag",IntHelpCheck) else -- QUAY Certainty can be "0" ; "1" ; "2" ; "3" ; local tblCertainty = {"Unreliable";"Questionable";"Secondary evidence";"Primary evidence";} local strCertainty = tblCertainty[tonumber(strVal)+1] if strCertainty == nil then doReportWarning(strTag,strVal,"Unrecognised 'Certainty' value",IntHelpReport) else if not fhSetValueAsText(ptrNew,strCertainty) then doReportWarning(strTag,strCertainty,"Certainty text not accepted",IntHelpCheck) end end doCopyChildrenItems(ptrOrigin,ptrNew) end return nil end -- local function doConvertUDF_Quay local function doConvertUDF_Conc_Cont() -- CONC & CONT are always invalid if intTag == 2 and ( fhIsEvent(ptrTarget) or fhGetTag(ptrTarget) == "RESI" ) then strTag = "SOUR2" -- If at target level 2 of Event or RESI then return strTag -- Create SOUR2 Note as if Attribute value end local strValue = "\n" -- CONTinuation on a new line if strTag == "CONC" then strValue = "" -- CONCatenation on same line end local strValue = fhGetValueAsText(ptrTarget)..strValue..strVal if not fhSetValueAsText(ptrTarget,strValue) then -- Append to existing Target value doReportWarning(strTag,strValue,"Value text not accepted",IntHelpCheck) end if fhGetValueAsText(ptrTarget) ~= strValue then -- "\n" does not work for Attribute value ( nor ADDR tag !!!! ) strValue = fhGetValueAsText(ptrTarget).."~"..strVal if not fhSetValueAsText(ptrTarget,strValue) then doReportWarning(strTag,strValue,"Value text not accepted",IntHelpCheck) end end return nil end -- local function doConvertUDF_Conc_Cont if fhIsUDF(ptrOrigin) then if strTag == "SOUR" or strTag == "OBJE" or strTag == "NOTE" then -- Convert UDF SOUR/OBJE/NOTE link or text if doConvertUDF_Link() == nil then return end elseif strTag == "DATE" or strTag == "_DATE" then -- Convert UDF DATE/_DATE string to DATE object if doConvertUDF_Date() == nil then return end elseif strTag == "EVEN" then -- Convert UDF EVEN string if doConvertUDF_Even() == nil then return end elseif strTag == "QUAY" then -- Convert UDF QUAY string if doConvertUDF_Quay() == nil then return end elseif strTag == "CONC" or strTag == "CONT" then -- Convert UDF CONC or CONT tag if doConvertUDF_Conc_Cont() == nil then return end end end -- No existing Child Tag so create new Child Tag if strTag == "TYPE" then -- Transfer any TYPE Descriptor to a SOURce Note ptrNew = fhCreateItem("SOUR2",ptrTarget) -- Because it cannot transfer to Custom Facts if ptrNew:IsNull() then doReportWarning("SOUR2",strVal,"Invalid Tag",IntHelpCheck) else if not fhSetValueAsText(ptrNew,"TYPE Descriptor: "..strVal) then doReportWarning("SOUR2","TYPE Descriptor: "..strVal,"Value text not accepted",IntHelpCheck) end end else ptrNew = fhCreateItem(strTag,ptrTarget,true) -- V5.0.2 lets existing empty items to be used, e.g. FILE or FORM auto-created by local OBJEct parent, if ptrNew:IsNull() then -- but FILE may be _FILE within OBJE within UDF, so try Tag without leading underscore. ptrNew = fhCreateItem(strTag:gsub("^_",""),ptrTarget,true) end if ptrNew:IsNull() then doReportWarning(strTag,strVal,"Invalid Tag",IntHelpCheck) else -- DATA & OBJE2 tags within UDF have null string text value that causes fhSetValue_Copy to fail if not ( fhGetValueType(ptrOrigin) == "text" and strVal == "" ) then local strValue = fhGetValueAsText(ptrOrigin) if not fhSetValue_Copy(ptrNew,ptrOrigin) then -- Otherwise just copy the Tag and Value doReportWarning(strTag,fhGetValueAsText(ptrOrigin),"Value not accepted",IntHelpCheck) end end end end doCopyChildrenItems(ptrOrigin,ptrNew) end -- local function doCopyChildBranch intTag = intTag + 1 -- Compile table of New Tag Data Reference hierarchy tblTag[intTag] = fhGetTag(ptrNew) local ptrOrigin = fhNewItemPtr() ptrOrigin = ptrOld:Clone() ptrOrigin:MoveToFirstChildItem(ptrOrigin) while ptrOrigin:IsNotNull() do doCopyChildBranch(ptrOrigin,ptrNew) -- Copy any Child Branch data ptrOrigin:MoveNext() end tblTag[intTag] = nil intTag = intTag - 1 end -- local function doCopyChildrenItems -- Delete Item and Report if Fails -- local function doDeleteItem(ptrTag,strVal,strFunc) local strTag = fhGetTag(ptrTag) if not fhDeleteItem(ptrTag) then doReportWarning(strTag,strVal,"fhDeleteItem failed in "..strFunc,IntHelpReport) end end -- local function doDeleteItem -- Copy the Tag Value -- local function flgCopyTagValue() local strTag = fhGetTag(ptrOld) local strVal = fhGetValueAsText(ptrOld) local ptrLnk = fhGetValueAsLink(ptrOld) local intVal = fhGetValueAsInteger(ptrOld) local dtDate = fhGetValueAsDate(ptrOld) local strOldText = fhGetItemText(ptrOld,strTag) local strOldType = fhGetValueType(ptrOld) -- Type strings "", "text", "link", "integer", "date" local strOldClas = fhGetDataClass(ptrOld) local strNewType = fhGetValueType(ptrNew) local strNewClas = fhGetDataClass(ptrNew) -- Omit all Event/RESIdence Facts where strVal == strOldType == "" if strOldType ~= "" then if strOldType == "text" then -- Attribute Fact or UDF Tag if strVal == "" then return true end local strLnk -- Try to convert old Tag text Value into a Link Pointer ptrLnk,strLnk = PtrStrTranslateLink(strVal) if ptrLnk:IsNotNull() then strOldType = "link" strTag = strLnk else intVal = tonumber(strVal) -- Try to convert old Tag text Value into an Integer if intVal ~= nil and strNewType == "integer" then strOldType = "integer" elseif dtDate:SetValueAsText(strVal) -- Try to convert old Tag text Value into a Date Object and strNewType == "date" then strOldType = "date" end end end if strOldType == "link" and ( fhIsAttribute(ptrNew) or fhIsEvent(ptrNew) ) then local ptrNew = fhCreateItem(strTag,ptrNew) -- Create a Fact child SOUR/OBJE/NOTE Record link if ptrNew:IsNull() then doReportWarning(strTag,strVal,"Invalid Tag ~ Tag Skipped",IntHelpCheck) return false else if not fhSetValueAsLink(ptrNew,ptrLnk) then -- Set the Link Pointer as its Value doReportWarning(strTag,strVal,"Invalid Link ~ Tag Skipped",IntHelpCheck) doDeleteItem(ptrNew,strVal,"flgCopyTagValue") return false end end elseif strNewType == "text" then -- Set new Tag value to old Tag value converted to Text if not fhSetValueAsText(ptrNew,strOldText) then doReportWarning(strTag,strOldText,"Invalid Text for "..strNew.." ~ Tag Skipped",IntHelpCheck) return false end elseif strNewType == "" then -- New Tag is an Event, or RESIdence, so create SOURce Note for Value local ptrVal = fhNewItemPtr() ptrVal = fhCreateItem("SOUR2",ptrNew) if ptrVal:IsNull() then doReportWarning(strTag,strOldText,"Invalid Tag ~ Tag Skipped",IntHelpCheck) return false else -- Set new SOURce Note value to old Tag name and value converted to Text if not fhSetValueAsText(ptrVal,StrOldData.."="..strOldText) then doReportWarning(strTag,strOldText,"Invalid Text ~ Tag Skipped",IntHelpCheck) doDeleteItem(ptrVal,strOldText,"flgCopyTagValue") return false end end elseif strNewType == strOldType then if strNewType == "link" then -- Set the Link Pointer as current Tag value if not fhSetValueAsLink(ptrNew,ptrLnk) then doReportWarning(strTag,strOldText,"Invalid Link for "..strNew.." ~ Tag Skipped",IntHelpCheck) return false end elseif strNewType == "integer" then -- Set the Integer Value as current Tag value if not fhSetValueAsInteger(ptrNew,intVal) then doReportWarning(strTag,strOldText,"Invalid Integer for "..strNew.." ~ Tag Skipped",IntHelpCheck) return false end elseif strNewType == "date" then -- Set the Date Object as current Tag value if not fhSetValueAsDate(ptrNew,dtDate) then doReportWarning(strTag,strOldText,"Invalid Date for "..strNew.." ~ Tag Skipped",IntHelpCheck) return false end else doReportWarning(strTag,strOldText,"Invalid Tag Type: "..strNewType.." ~ Tag Skipped",IntHelpReport) return false end else if not fhSetValue_Copy(ptrNew,ptrOld) then -- Expected to fail doReportWarning(strTag,strVal,"Invalid value for "..strNew.." ~ Tag Skipped",IntHelpCheck) return false end end end return true end -- local function flgCopyTagValue if strNew == StrDeleteTag then -- Delete the old Tag from Record WriteDataChange(ptrRecord,ptrOld,strNew,StrDeleteTag) doDeleteItem(ptrOld,StrDeleteTag,"DoEditDataDetails") return end ptrNew = fhCreateItem(strNew,ptrRecord) -- Ensure the New Tag has been created if ptrNew:IsNull() then doReportWarning(strNew,TblDictionary[fhGetTag(ptrRecord)][strNew],"Cannot be created ~ Tag Skipped",IntHelpCheck) else if not fhMoveItemBefore(ptrNew,ptrOld) then -- Ensure the Tag order is maintained doReportWarning(strNew,TblDictionary[fhGetTag(ptrRecord)][strNew],"Cannot be moved ~ Tag Skipped",IntHelpCheck) doDeleteItem(ptrNew,"MoveItemBefore","DoEditDataDetails") else if not flgCopyTagValue() then -- Transfer old Tag value to new Tag value, or new SOURce Note doDeleteItem(ptrNew,"CopyTagValue","DoEditDataDetails") else doCopyChildrenItems(ptrOld,ptrNew) -- Copy the Child Tag structure WriteDataChange(ptrRecord,ptrOld,ptrNew,"") -- Log the Change to file doDeleteItem(ptrOld,"CopyTagValue","DoEditDataDetails") -- Delete the Old Tag from Record for intItem, strLog in ipairs(tblLog) do WriteLogFile("Warning\t"..strLog.."\r\n") -- Log each Warning Message to file local ptrVal = fhNewItemPtr() ptrVal = fhCreateItem("SOUR2",ptrNew) -- Copy each Warning into Tag SOURce Note if ptrVal:IsNull() then ptrVal = fhCreateItem("SOUR2",ptrRecord) -- or into whole Record SOURce Note if ptrVal:IsNull() then ptrVal = fhCreateItem("NOTE2",ptrRecord) -- or into whole Record local NOTE if ptrVal:IsNull() then doReportWarning("SOUR2/NOTE2","strLog","Warning cannot be created",IntHelpReport) return end end end if not fhSetValueAsText(ptrVal,strLog) then -- Save the Warning in Source/Local Note doReportWarning("SOUR2/NOTE2",strLog,"Warning cannot be saved",IntHelpReport) end end return end end end WriteDataChange(ptrRecord,ptrOld,ptrNew,StrSkipTag) -- Log the Skip to file for intItem, strLog in ipairs(tblLog) do WriteLogFile("Warning\t"..strLog.."\r\n") -- Log each Warning Message to file end end -- function DoEditDataDetails -- Check if Filters Match the Selected Tag -- function FlgFiltersMatch(ptrTag,tblFilter) local flgFilter -- Determines whether Plain=true or Pattern=false filter matching if StrPattern == "OFF" then flgFilter = true else flgFilter = false end -- Check if Filter Value matches Record Value and fail if no match if string.find(fhGetItemText(ptrTag,fhGetTag(ptrTag)),tblFilter[IntFilterValue],1,flgFilter) == nil then return false end local strMatchDate = tblFilter[IntFilterDate] local flgMatchDate -- Determines if Filter Date matches a DATE field local strMatchPlace = tblFilter[IntFilterPlace] local flgMatchPlace -- Determines if Filter Place matches a PLAC field -- An empty Filter always matches, else if no field exists then it never matches if strMatchDate == "" then flgMatchDate = true else flgMatchDate = false end if strMatchPlace == "" then flgMatchPlace = true else flgMatchPlace = false end if not ( flgMatchDate and flgMatchPlace ) then -- Search the Child Tags for other Filters local ptrChild = fhNewItemPtr() ptrChild:MoveToFirstChildItem(ptrTag) while ptrChild:IsNotNull() do local strTag = fhGetTag(ptrChild) -- V2.6 if strTag == "DATE" and not flgMatchDate then -- Check if Filter Date matches DATE value and fail if no match if string.find(fhGetItemText(ptrChild,"DATE:COMPACT"),strMatchDate,1,flgFilter) == nil -- V2.5 added :COMPACT and string.find(fhGetItemText(ptrChild,"DATE:LONG"),strMatchDate,1,flgFilter) == nil then return false end flgMatchDate = true end if strTag:match("^_?PLAC$") and not flgMatchPlace then -- Check if Filter Place matches PLACe/_PLACe value and fail if no match -- V2.6 cater for _PLAC if string.find(fhGetValueAsText(ptrChild),strMatchPlace,1,flgFilter) == nil and string.find(fhGetValueAsText(fhGetItemPtr(ptrTag,"~._PLAC")),strMatchPlace,1,flgFilter) == nil then return false end -- V2.6 _PLAC flgMatchPlace = true end ptrChild:MoveNext() end if not ( flgMatchDate and flgMatchPlace ) then -- DATE or PLAC not matched at Child level so check Grandchild level ptrChild:MoveToFirstChildItem(ptrTag) while ptrChild:IsNotNull() do local ptrGrandChild = fhNewItemPtr() ptrGrandChild:MoveToFirstChildItem(ptrChild) while ptrGrandChild:IsNotNull() do local strTag = fhGetTag(ptrGrandChild) -- V2.6 if strTag == "DATE" and not flgMatchDate then -- Check if Filter Date matches DATE value and fail if no match if string.find(fhGetItemText(ptrGrandChild,"DATE:COMPACT"),strMatchDate,1,flgFilter) == nil -- V2.5 added :COMPACT and string.find(fhGetItemText(ptrGrandChild,"DATE:LONG"),strMatchDate,1,flgFilter) == nil then return false end flgMatchDate = true end if strTag == "PLAC" and not flgMatchPlace then -- Check if Filter Place matches PLACe value and fail if no match if string.find(fhGetValueAsText(ptrGrandChild),strMatchPlace,1,flgFilter) == nil then return false end flgMatchPlace = true end ptrGrandChild:MoveNext() end ptrChild:MoveNext() end end end return flgMatchDate and flgMatchPlace end -- function FlgFiltersMatch -- Change Selected Tag -- function DoChangeData(strRecTag,strOldSet,strNewSet,strOldTag,strNewTag,tblFilter) -- strRecTag = "INDI","FAM","SOUR",etc record type of Tag to change/delete -- strOldSet = Source Tag Set of Source Tag to change from or delete -- strNewSet = Target Tag Set of Target Tag to change into -- strOldTag = Source Tag Name to change from, or to delete -- strNewTag = Target Tag Name to change into, or StrDeleteTag to delete Source -- tblFilter = List of Filter Value, Date, Place text strings, and Records -- Iterate selected or all Records -- -- V2.9 local function ptrRecords(strRecTag,arrRecords) local ptrRec = fhNewItemPtr() local ptrNxt = fhNewItemPtr() local intRec if #arrRecords > 0 then intRec = 1 ptrRec = arrRecords[intRec] -- 1st selected record else ptrRec:MoveToFirstRecord(strRecTag) -- 1st of all records end return function () ptrNxt = ptrRec:Clone() -- Next record to return if intRec then intRec = intRec + 1 -- Next selected record ptrRec = arrRecords[intRec] or fhNewItemPtr() else ptrRec:MoveNext() -- Next of all records end if ptrNxt:IsNotNull() then return ptrNxt end end end -- local function ptrRecords -- Translate Tag to its Name -- local function strTranslateTag(strTag) local strData if strTag == StrDeleteTag then return strTag end strData = TblDictionary[strRecTag][strTag] strData = strData:replace("_ATTR-","") strData = strData:replace("FACT-","") -- FH V7 -- V3.1 strData = strData:replace("EVEN-","") return strData end -- local function strTranslateTag StrOldData = strTranslateTag(strOldTag) StrNewData = strTranslateTag(strNewTag) local flgConvert = true -- Enable the Tag change/delete conversion operations local flgConfirm = ( StrConfirm == "ON" ) -- Enable/disable the GUI Data confirmation dialogue local ptrOldTag = fhNewItemPtr() -- Pointer to source Tag local ptrGotTag = fhNewItemPtr() -- Pointer to current Tag local ptrNewTag = fhNewItemPtr() -- Pointer to target Tag local ptrRecord = fhNewItemPtr() -- Pointer to current Record local intTagTotal = 0 -- Total matching source Tags for ptrRecord in ptrRecords(strRecTag,tblFilter[IntFilterRecord]) do -- V2.9 ptrOldTag:MoveTo(ptrRecord,"~."..strOldTag) while ptrOldTag:IsNotNull() do intTagTotal = intTagTotal + 1 -- Count matching source Tags ptrOldTag:MoveNext("SAME_TAG") end end if intTagTotal > 100 and not flgConfirm then progbar.Start("Editing Tags",intTagTotal) -- Optionally start Progress Bar end local intTagCount = intTagTotal -- Count pending source Tags IntSkippedData = 0 -- Count skipped source Tags IntDeletedData = 0 -- Count deleted source Tags IntChangedData = 0 -- Count changed source Tags IntWarningData = 0 -- Count warning Messages if tblFilter[IntFilterValue]..tblFilter[IntFilterDate]..tblFilter[IntFilterPlace] ~= "" then WriteLogFile("Filters\tValue="..tblFilter[IntFilterValue].." Date="..tblFilter[IntFilterDate].." Place="..tblFilter[IntFilterPlace].."\r\n") end for ptrRecord in ptrRecords(strRecTag,tblFilter[IntFilterRecord]) do -- V2.9 ptrOldTag:MoveTo(ptrRecord,"~."..strOldTag) while ptrOldTag:IsNotNull() do ptrGotTag = ptrOldTag:Clone() -- Clone got Tag from old Tag ptrOldTag:MoveNext("SAME_TAG") if FlgFiltersMatch(ptrGotTag,tblFilter) then if flgConfirm then -- Prompt user to choose action flgConvert,flgConfirm = GUI_DataDialogue(ptrRecord,ptrGotTag,strNewTag) if not flgConfirm then if not flgConvert then break end -- Cancel all edits if intTagCount > 100 then -- Optionally start Progress Bar progbar.Start("Editing Tags",intCount) end end end if flgConvert then -- Change/Delete the old Tag in the Record DoEditDataDetails(ptrRecord,ptrGotTag,strNewTag) else -- Skip the old Tag in the Record WriteDataChange(ptrRecord,ptrGotTag,ptrNewTag,StrSkipTag) end end intTagCount = intTagCount - 1 -- Move onto next Tag progbar.Step(1) progbar.Message(strRecTag.." Rec Id "..fhGetRecordId(ptrRecord)) end if progbar.Stop() then break end -- Cancel all edits? if not flgConfirm and not flgConvert then break end end progbar.Close() local strOldData = strOldTag..": "..StrOldData local strSkipped = "" local strMessage = "" if intTagCount > 0 then -- Some items were skipped by cancelling IntSkippedData = IntSkippedData + intTagCount IntSkippedTotal = IntSkippedTotal + intTagCount strSkipped = "Edit cancelled and "..intTagCount.." '"..strOldData.."' items skipped.\r\n" if intTagCount == 1 then strSkipped = strSkipped:gsub(" items "," item ") end WriteLogFile("\t"..strSkipped) strSkipped = strSkipped.."\r\n" end if IntSkippedData + IntDeletedData + IntChangedData == 0 then strMessage = "No '"..strOldData.."' items edited.\r\n" else strMessage = "Finished '"..strOldData.."' item editing.\r\n" end WriteLogFile("\t"..strMessage) iup_gui.CustomDialogue("Memo","0x0") -- Custom "Memo" dialogue minimum size & centralised iup_gui.MemoDialogue(strSkipped..strMessage.."\n"..UpdateCountStatus()) if IntChangedData > 0 then -- Insert the New Tag into the Old Tag index and update dropdown lists DoInsertTagIndex(strRecTag,strNewSet,strNewTag,StrModeOld) StrPopulateDropdownLists(strRecTag) -- 17 Aug 2013 end if ( IntDeletedData + IntChangedData ) == intTagTotal then -- Remove the Old Tag from the Old Tag index and update dropdown lists DoRemoveTagIndex(strRecTag,strOldSet,strOldTag) StrPopulateDropdownLists(strRecTag) -- 17 Aug 2013 end end -- function DoChangeData -- Populate the Record Type dropdown list -- function StrPopulateRecordDropList() -- Preserve currently selected Record Type local strOldRec = iup.GetAttribute(LstRecords,tostring(LstRecords.Value)) local intOldRec = 0 LstRecords.RemoveItem = nil for intRecTag,strRecTag in ipairs(TblRecordTag) do if strRecTag ~= "INDI" and strRecTag ~= "FAM" then for intItem,strSet in ipairs(TblSetNameIndex[strRecTag]) do if FlgTableHasData(TblOldTagIndex[strRecTag][strSet]) then local strNewRec = TblRecordName[intRecTag] LstRecords.AppendItem = strNewRec -- Add the Record Type because it has associated Set names with associated Tags intOldRec = intOldRec + 1 if strNewRec == strOldRec then LstRecords.Value = intOldRec -- Select this Record Type if it was selected before end -- Works because TblRecordTag determines order in LstRecords which is not sorted break end end end end if LstRecords.Value == "0" then LstRecords.Value = 1 end return TblDictionary[iup.GetAttribute(LstRecords,tostring(LstRecords.Value))] end -- function StrPopulateRecordDropList -- Populate any pair of Tag Set dropdown lists -- function StrStrPopulateSetDropList(strRecTag,lstOldDrop,lstNewDrop) -- Preserve currently selected Set Names local strOldSet = iup.GetAttribute(lstOldDrop,tostring(lstOldDrop.Value)) local strNewSet = iup.GetAttribute(lstNewDrop,tostring(lstNewDrop.Value)) local intOldSet = 0 local intNewSet = 0 lstOldDrop.RemoveItem = nil lstNewDrop.RemoveItem = nil for intItem,strSet in ipairs(TblSetNameIndex[strRecTag]) do if FlgTableHasData(TblOldTagIndex[strRecTag][strSet]) then lstOldDrop.AppendItem = strSet -- Add the Set because it has associated Tags intOldSet = intOldSet + 1 if strSet == strOldSet then lstOldDrop.Value = intOldSet -- Select this Set if it was selected before end -- Works because TblSetNameIndex determines order in lstOldDrop which is not sorted end if FlgTableHasData(TblNewTagIndex[strRecTag][strSet]) and strSet ~= StrUDFTagsSet then lstNewDrop.AppendItem = strSet -- Add the Set because it has associated Tags, unless it is the UDF Tags Set intNewSet = intNewSet + 1 if strSet == strNewSet then lstNewDrop.Value = intNewSet -- Select this Set if it was selected before end -- Works because TblSetNameIndex determines order in lstNewDrop which is not sorted end end if lstOldDrop.Value == "0" then lstOldDrop.Value = 1 end if lstNewDrop.Value == "0" then lstNewDrop.Value = 1 end return iup.GetAttribute(lstOldDrop,tostring(lstOldDrop.Value)),iup.GetAttribute(lstNewDrop,tostring(lstNewDrop.Value)) end -- function StrStrPopulateSetDropList -- Populate any Tag Name dropdown list -- function DoPopulateNameDropList(strRecTag,tblTagIndex,lstDrop) -- Preserve currently selected Tag Name local strOldTag = iup.GetAttribute(lstDrop,tostring(lstDrop.Value)) local intOldTag = 0 lstDrop.RemoveItem = nil for intTag,strTag in ipairs(tblTagIndex) do local strNewTag = TblDictionary[strRecTag][strTag] lstDrop.AppendItem = strNewTag intOldTag = intOldTag + 1 end for intOldTag = 1, intOldTag do if strOldTag == iup.GetAttribute(lstDrop,tostring(intOldTag)) then lstDrop.Value = intOldTag -- Select this Tag Name if it was selected before break -- Need this technique because tblTagIndex is not same order as lstDrop sorted order end end -- Leave no Tag Name selected if nothing has matched end -- function DoPopulateNameDropList -- Populate the Dropdown lists on current Tab -- function StrPopulateDropdownLists(strRecTag) -- 17 Aug 2013 intChosenTab > strRecTag local strOldSet, strNewSet if strRecTag == "INDI" then -- Individual Records Tab -- Add each Fact Set name to Individual dropdown list strOldSet, strNewSet = StrStrPopulateSetDropList(strRecTag,LstIndOldSet,LstIndNewSet) -- Add each Fact Name to Individual dropdown lists DoPopulateNameDropList(strRecTag,TblOldTagIndex[strRecTag][strOldSet],LstIndOldTag) DoPopulateNameDropList(strRecTag,TblNewTagIndex[strRecTag][strNewSet],LstIndNewTag) elseif strRecTag == "FAM" then -- Family Records Tab -- Add each Fact Set name to Family dropdown list strOldSet, strNewSet = StrStrPopulateSetDropList(strRecTag,LstFamOldSet,LstFamNewSet) -- Add each Fact Name to Family dropdown lists DoPopulateNameDropList(strRecTag,TblOldTagIndex[strRecTag][strOldSet],LstFamOldTag) DoPopulateNameDropList(strRecTag,TblNewTagIndex[strRecTag][strNewSet],LstFamNewTag) elseif type(strRecTag) == "string" then -- Other Records Tab strRecTag = StrPopulateRecordDropList() -- Add each Tag Set name to Record dropdown lists strOldSet, strNewSet = StrStrPopulateSetDropList(strRecTag,LstRecOldSet,LstRecNewSet) -- Add each Tag Name to Record dropdown lists DoPopulateNameDropList(strRecTag,TblOldTagIndex[strRecTag][strOldSet],LstRecOldTag) DoPopulateNameDropList(strRecTag,TblNewTagIndex[strRecTag][strNewSet],LstRecNewTag) end return strRecTag end -- function StrPopulateDropdownLists -- GUI View Log File Dialogue -- function GUI_LogsDialogue(tblLogFile) -- Create each GUI text and button with title, etc local txtView = iup.text { ReadOnly="YES"; MultiLine="YES"; AutoHide="YES"; VisibleColumns="52"; VisibleLines="10"; } local btnOldest = iup.button { Title="Oldest Log File"; } local btnPrior = iup.button { Title="Prior Log File" ; } local btnDelete = iup.button { Title="Delete Log File"; } local btnNext = iup.button { Title="Next Log File" ; } local btnLatest = iup.button { Title="Latest Log File"; } local btnClose = iup.button { Title="Close View && Delete Log Files"; Expand="NO"; } -- Create dialogue and turn off resize, maximize, minimize, and menubox except Close button local dialogLogs = iup.dialog { Title=Utf8PluginTitle.." View & Delete Log Files"; iup.vbox { Alignment="ACENTER"; Gap=iup_gui.Gap; Margin=iup_gui.Margin; iup.frame { Font=iup_gui.FontHead; Title="View & Delete Log Files"; iup.vbox { txtView; iup.hbox { Expand="HORIZONTAL"; Homogeneous="YES"; btnOldest; btnPrior; btnDelete; btnNext; btnLatest; Margin=iup_gui.Margin; }; iup.hbox { Expand="HORIZONTAL"; Homogeneous="YES"; iup.fill{}; btnClose; iup.fill{}; }; }; }; }; } local intLogFile = tblLogFile[0] -- Number of log files local function doDeleteLogFile() -- Action for Delete Log File button local fileHandle, strError = os.remove(tblLogFile[intLogFile]) if fileHandle == nil then iup.Message(Utf8PluginTitle, "Old log file not deleted!\n"..strError) else if tblLogFile[intLogFile] == AnsiLogFilePath then LblLogFile.Title = "Log File not created." -- Current log file deleted so update Main GUI LogFileHandle = nil end table.remove(tblLogFile,intLogFile) -- Remove log file name from table tblLogFile[0] = tblLogFile[0] - 1 -- Decrement number of log file names end end -- local function doDeleteLogFile local function doViewLogFile(strLogFile) -- Display Log File contents in multiline text box local strValue = "" for strLine in encoder.FileLines(strLogFile) do strValue = strValue..strLine.."\n" end txtView.Value = strValue end -- local function doViewLogFile local function doUpdateDisplay() -- Update buttons and multiline text box if tblLogFile[0] <= 0 then tblLogFile[0] = 0 -- No log files exist, so all buttons inactive intLogFile = 0 btnOldest.Active = "NO" btnPrior.Active = "NO" btnDelete.Active = "NO" btnNext.Active = "NO" btnLatest.Active = "NO" txtView.Value = "No Log Files" elseif tblLogFile[0] == 1 then -- One log file exists, so only Delete button active intLogFile = 1 btnOldest.Active = "NO" btnPrior.Active = "NO" btnDelete.Active = "YES" btnNext.Active = "NO" btnLatest.Active = "NO" doViewLogFile(tblLogFile[intLogFile]) else btnOldest.Active = "YES" btnPrior.Active = "YES" btnDelete.Active = "YES" btnNext.Active = "YES" btnLatest.Active = "YES" if intLogFile == 1 then -- Oldest log file selected, so cannot go backward btnOldest.Active = "NO" btnPrior.Active = "NO" elseif intLogFile >= tblLogFile[0] then -- Latest log file selected, so cannot go forward intLogFile = tblLogFile[0] btnNext.Active = "NO" btnLatest.Active = "NO" end doViewLogFile(tblLogFile[intLogFile]) end end -- local function doUpdateDisplay -- Set other GUI control attributes local tblControls={ { "Font"; "FgColor"; "Tip"; "action"; {"TipBalloon";"Balloon";}; {"Expand";"YES";}; doUpdateDisplay; }; [txtView] = { "FontBody"; "Body"; "Selected log file details" ; false; }; [btnOldest] = { "FontBody"; "Safe"; "Select oldest log file" ; function() intLogFile=1 doUpdateDisplay() end; }; [btnPrior] = { "FontBody"; "Safe"; "Select prior log file" ; function() intLogFile=intLogFile-1 doUpdateDisplay() end; }; [btnDelete] = { "FontBody"; "Warn"; "Delete selected log file" ; function() doDeleteLogFile() doUpdateDisplay() end; }; [btnNext] = { "FontBody"; "Safe"; "Select next log file" ; function() intLogFile=intLogFile+1 doUpdateDisplay() end; }; [btnLatest] = { "FontBody"; "Safe"; "Select latest log file" ; function() intLogFile=tblLogFile[0] doUpdateDisplay() end; }; [btnClose] = { "FontBody"; "Risk"; "Close this View and Delete Log Files window"; function() return iup.CLOSE end; }; } iup_gui.AssignAttributes(tblControls) -- Assign GUI control attributes iup_gui.ShowDialogue("Logs",dialogLogs,btnClose,"normal keep") end -- function GUI_LogsDialogue -- GUI Warnings Dialogue -- function GUI_WarnDialogue(strName,strData,strWarn,intHelp) local tblHelp = {"Check that the Source and Target are compatible";"Data value is logged in a Source Note";"Please report via FHUG Forum > Plugins Discussion"} -- Create each GUI label and button with title, etc local lblNameHead = iup.label { Title="Name: " ; Alignment="ARIGHT"; Size="50"; } local lblNameText = iup.label { Title=strName; } local lblDataHead = iup.label { Title="Data: " ; Alignment="ARIGHT"; Size="50"; } local lblDataText = iup.label { Title=strData; } local lblWarnHead = iup.label { Title="Warning: "; Alignment="ARIGHT"; Size="50"; } local lblWarnText = iup.label { Title=strWarn; } local lblHelpText = iup.label { Title=tblHelp[intHelp]; Alignment="ACENTER"; } local btnClose = iup.button { Title="Close Warning"; action=function() return iup.CLOSE end; } -- Create dialogue and turn off resize, maximize, minimize, and menubox except Close button local dialogWarn = iup.dialog { Title=Utf8PluginTitle.." Warning"; iup.vbox { Alignment="ACENTER"; Gap=iup_gui.Gap; Margin=iup_gui.Border; iup.frame { Font=iup_gui.FontHead; FgColor=iup_gui.Head; Title="Warning"; iup.vbox { Margin=iup_gui.Margin; iup.hbox { lblNameHead; lblNameText; }; iup.hbox { lblDataHead; lblDataText; }; iup.hbox { lblWarnHead; lblWarnText; }; lblHelpText; btnClose; }; }; }; } -- Set other GUI control attributes local tblControls={ { "Font"; "FgColor"; "Expand"; "Tip"; {"TipBalloon";"Balloon";}; }; [lblNameHead] = { "FontBody"; "Body"; "VERTICAL" ; "Record name"; }; [lblNameText] = { "FontBody"; "Body"; "HORIZONTAL"; "Record name"; }; [lblDataHead] = { "FontBody"; "Body"; "VERTICAL" ; "Record data"; }; [lblDataText] = { "FontBody"; "Body"; "HORIZONTAL"; "Record data"; }; [lblWarnHead] = { "FontBody"; "Risk"; "VERTICAL" ; "Warning message"; }; [lblWarnText] = { "FontBody"; "Risk"; "HORIZONTAL"; "Warning message"; }; [lblHelpText] = { "FontBody"; "Safe"; "YES" ; "Advice message" ; }; [btnClose] = { "FontBody"; "Risk"; "YES" ; "Close this Warning window"; }; } iup_gui.AssignAttributes(tblControls) -- Assign GUI control attributes iup_gui.ShowDialogue("Memo",dialogWarn,btnClose,"normal keep") end -- function GUI_WarnDialogue -- GUI Data Dialogue -- function GUI_DataDialogue(ptrRecord,ptrOldData,strNewTag) local bEdit = false -- true means Edit Tags & false means Skip Tags local bConf = true -- true means confirm next Tag Edit & false means do the rest local strCol = "Body" -- Colour of lblEditHead/Text depends on strEditWarn local strEditText = "" -- Text statement of options local strEditWarn = "" -- Warning message makes text red local strEditData = "" -- Delete/Change this Tag button label local strEditRest = "" -- Delete/Change the Rest button label local strOldType = "" -- Source Tag type is "Event" or "Attribute" or "Tag" local strNewType = "" -- Target Tag type is "Event" or "Attribute" or "Tag" local ptrNewData = fhNewItemPtr() -- Determine if Fact Event or Fact Attribute or Data Tag -- local function strDataType(ptrData) local strType if fhIsAttribute(ptrData) then strType = "Attribute" elseif fhIsEvent(ptrData) then strType = "Event" elseif fhIsUDF(ptrData) then if StrOldData:sub(-5) == "Event" then strType = "Event" elseif StrOldData:sub(-9) == "Attribute" then strType = "Attribute" else strType = "Tag" end else strType = "Tag" end return strType end -- local function strDataType strOldType = strDataType(ptrOldData) if strNewTag == StrDeleteTag then strEditText = "Delete this "..strOldType.." ?"..strEditText strEditData = "Delete this "..strOldType strEditRest = "Delete all the Rest" else ptrNewData = fhCreateItem(strNewTag,ptrRecord) strNewType = strDataType(ptrNewData) fhDeleteItem(ptrNewData) if strOldType == "Attribute" and fhGetTag(ptrOldData) ~= "RESI" and ( strNewType == "Event" or strNewTag == "RESI" ) then strEditWarn = "\nAny Attribute value will migrate to a SOURce Note !" strCol = "Risk" end strEditText = "Change this "..strOldType.." to "..strNewTag..": "..StrNewData.." "..strNewType.." ?"..strEditWarn..strEditText strEditData = "Change to "..StrNewData.." "..strNewType strEditRest = "Change all the Rest" end -- Create each GUI label and button with title, etc local lblNameHead = iup.label { Title="Name: " ; Alignment="ARIGHT"; Size="50"; } local lblNameText = iup.label { Title=StrDisplayName(ptrRecord); } local lblDataHead = iup.label { Title="Data: " ; Alignment="ARIGHT"; Size="50"; } local lblDataText = iup.label { Title=StrDisplayData(ptrOldData,StrOldData); } local lblEditHead = iup.label { Title="Option: "; Alignment="ARIGHT"; Size="50"; } local lblEditText = iup.label { Title=strEditText; } local btnEditData = iup.button { Title=strEditData; } local btnSkipData = iup.button { Title="Skip this "..strOldType; } local btnEditRest = iup.button { Title=strEditRest; } local btnSkipRest = iup.button { Title="Skip all the Rest"; } -- Create dialogue and turn off resize, maximize, minimize, and menubox except Close button local dialogData = iup.dialog { Title=Utf8PluginTitle.." Confirm Edit"; iup.vbox { Alignment="ACENTER"; Gap=iup_gui.Gap; Margin=iup_gui.Border; iup.frame { Font=iup_gui.FontHead; FgColor=iup_gui.Head; Title="Confirm Edit"; iup.vbox { Margin=iup_gui.Margin; iup.hbox { lblNameHead; lblNameText; }; iup.hbox { lblDataHead; lblDataText; }; iup.hbox { lblEditHead; lblEditText; }; iup.hbox { btnEditData; btnSkipData; Homogeneous="YES"; }; iup.hbox { btnEditRest; btnSkipRest; Homogeneous="YES"; }; }; }; }; close_cb=function() bEdit=false bConf=false end; -- Skip all Tag changes } -- Set other GUI control attributes local tblControls={ { "Font"; "FgColor"; "Expand"; "Tip"; "action"; {"TipBalloon";"Balloon";}; }; [lblNameHead] = { "FontBody"; "Body"; "VERTICAL" ; "Fact/Tag name"; }; [lblNameText] = { "FontBody"; "Body"; "HORIZONTAL"; "Fact/Tag name"; }; [lblDataHead] = { "FontBody"; "Body"; "VERTICAL" ; "Fact/Tag data"; }; [lblDataText] = { "FontBody"; "Body"; "HORIZONTAL"; "Fact/Tag data"; }; [lblEditHead] = { "FontBody"; strCol; "VERTICAL" ; "Fact/Tag edit"; }; [lblEditText] = { "FontBody"; strCol; "HORIZONTAL"; "Fact/Tag edit"; }; [btnEditData] = { "FontBody"; "Safe"; "YES" ; "Edit this Fact/Tag" ; function() bEdit=true bConf=true return iup.CLOSE end; }; [btnSkipData] = { "FontBody"; "Safe"; "YES" ; "Skip this Fact/Tag" ; function() bEdit=false bConf=true return iup.CLOSE end; }; [btnEditRest] = { "FontBody"; "Safe"; "YES" ; "Edit rest of Facts/Tags"; function() bEdit=true bConf=false return iup.CLOSE end; }; [btnSkipRest] = { "FontBody"; "Safe"; "YES" ; "Skip rest of Facts/Tags"; function() bEdit=false bConf=false return iup.CLOSE end; }; } iup_gui.AssignAttributes(tblControls) -- Assign GUI control attributes iup_gui.ShowDialogue("Memo",dialogData,btnSkipRest,"normal keep") return bEdit,bConf end -- function GUI_DataDialogue -- GUI Main Dialogue -- function GUI_MainDialogue() local flgQuitMode = false -- btnQuit.action() = true, and btnClose.action() = false local strRecTag = "INDI" -- Current Record Tag "INDI", "FAM", "NOTE", "SOUR", etc local strIndOldSet = StrStandard -- Individual Old Tag Set chosen local strIndOldTag = nil -- Individual Old Tag Name to be removed local strIndNewSet = StrStandard -- Individual New Tag Set chosen local strIndNewTag = nil -- Individual New Tag Name to be inserted local tblIndFilter = {"";"";"";{};} -- Individual Old Tag Filters for Value, Date, Place, Records -- V2.9 local strFamOldSet = StrStandard -- Family Old Tag Set chosen local strFamOldTag = nil -- Family Old Tag Name to be removed local strFamNewSet = StrStandard -- Family New Tag Set chosen local strFamNewTag = nil -- Family New Tag Name to be inserted local tblFamFilter = {"";"";"";{};} -- Family Old Tag Filters for Value, Date, Place, Records -- V2.9 local strRecOldSet = StrDefinedTags -- Record Old Tag Set chosen local strRecOldTag = nil -- Record Old Tag Name to be removed local strRecNewSet = StrDefinedTags -- Record New Tag Set chosen local strRecNewTag = nil -- Record New Tag Name to be inserted local tblRecFilter = {"";"";"";{};} -- Record Old Tag Filters for Value, Date, Place, Records -- V2.9 local strIndDelete = " Delete every 'Source Tag Name' chosen above " local strIndChange = " Change 'Source Tag Name' into 'Target Tag Name' " local strFamDelete = " Delete every 'Source Tag Name' chosen above " local strFamChange = " Change 'Source Tag Name' into 'Target Tag Name' " local strRecDelete = " Delete every 'Source Tag Name' chosen above " local strRecChange = " Change 'Source Tag Name' into 'Target Tag Name' " -- If a button title is too long, then truncate long names and add elipsis local function strButtonTitle(strRecTag,strOldTag,strMid,strNewTag) local strOld = "" local strNew = "" if strOldTag then -- V2.5 strOld = TblDictionary[strRecTag][strOldTag] end if strNewTag then -- V2.5 strNew = TblDictionary[strRecTag][strNewTag] end if ( #strOld + #strMid + #strNew ) > IntMaxNameLen * 2 - 4 then if #strOld > IntMaxNameLen then strOld = strOld:sub(1,IntMaxNameLen-3).."..." end if #strNew > IntMaxNameLen then strNew = strNew:sub(1,IntMaxNameLen-3).."..." end end return strOld..strMid..strNew end -- local function strButtonTitle local function iupRadio(tglA,tglB) -- Return radio handle created from two toggles return iup.radio { iup.hbox { tglA, tglB, Homogeneous="YES" }, Value=tglA } end -- local function iupRadio -- Create the Individual Records controls with titles/values and tooltips, etc local btnCHR_BAPM = iup.button { Title=strButtonTitle("INDI","CHR"," to ","BAPM") } local btnBAPM_CHR = iup.button { Title=strButtonTitle("INDI","BAPM"," to ","CHR") } local lblIndOldSet = iup.label { Title=" Source Tag Set:" , Alignment="ARIGHT", Size="80" } LstIndOldSet = iup.list { DropDown="YES", Sort="NO" , VisibleColumns="9", Visible_Items="16" } local lblIndNewSet = iup.label { Title=" Target Tag Set:" , Alignment="ARIGHT", Size="80" } LstIndNewSet = iup.list { DropDown="YES", Sort="NO" , VisibleColumns="9", Visible_Items="16" } local lblIndOldTag = iup.label { Title=" Source Tag Name:", Alignment="ARIGHT", Size="80" } LstIndOldTag = iup.list { DropDown="YES", Sort="YES", VisibleColumns="9", Visible_Items="16" } local lblIndNewTag = iup.label { Title=" Target Tag Name:", Alignment="ARIGHT", Size="80" } LstIndNewTag = iup.list { DropDown="YES", Sort="YES", VisibleColumns="9", Visible_Items="16" } local btnIndDelete = iup.button { Title=strIndDelete, Active="NO" } local btnIndChange = iup.button { Title=strIndChange, Active="NO" } local lblIndValue = iup.label { Title=" Value:" } local txtIndValue = iup.text { Value=tblIndFilter[IntFilterValue] } local lblIndDate = iup.label { Title=" Date:" } local txtIndDate = iup.text { Value=tblIndFilter[IntFilterDate] } local lblIndPlace = iup.label { Title=" Place:" } local txtIndPlace = iup.text { Value=tblIndFilter[IntFilterPlace] } local lblIndRecord = iup.label { Title=" Records: " } local tglIndRecAll = iup.toggle { Title="All" } -- V2.9 local tglIndRecSel = iup.toggle { Title="Pick" } -- V2.9 local radIndRecord = iupRadio ( tglIndRecAll, tglIndRecSel ) -- V2.9 -- Create the Family Records controls with titles/values and tooltips, etc local btnDIV_ANUL = iup.button { Title=strButtonTitle("FAM","DIV"," to ","ANUL") } local btnANUL_DIV = iup.button { Title=strButtonTitle("FAM","ANUL"," to ","DIV") } local lblFamOldSet = iup.label { Title=" Source Tag Set:" , Alignment="ARIGHT", Size="80" } LstFamOldSet = iup.list { DropDown="YES", Sort="NO" , VisibleColumns="9", Visible_Items="16" } local lblFamNewSet = iup.label { Title=" Target Tag Set:" , Alignment="ARIGHT", Size="80" } LstFamNewSet = iup.list { DropDown="YES", Sort="NO" , VisibleColumns="9", Visible_Items="16" } local lblFamOldTag = iup.label { Title=" Source Tag Name:", Alignment="ARIGHT", Size="80" } LstFamOldTag = iup.list { DropDown="YES", Sort="YES", VisibleColumns="9", Visible_Items="16" } local lblFamNewTag = iup.label { Title=" Target Tag Name:", Alignment="ARIGHT", Size="80" } LstFamNewTag = iup.list { DropDown="YES", Sort="YES", VisibleColumns="9", Visible_Items="16" } local btnFamDelete = iup.button { Title=strFamDelete, Active="NO" } local btnFamChange = iup.button { Title=strFamChange, Active="NO" } local lblFamValue = iup.label { Title=" Value:" } local txtFamValue = iup.text { Value=tblFamFilter[IntFilterValue] } local lblFamDate = iup.label { Title=" Date:" } local txtFamDate = iup.text { Value=tblFamFilter[IntFilterDate] } local lblFamPlace = iup.label { Title=" Place:" } local txtFamPlace = iup.text { Value=tblFamFilter[IntFilterPlace] } local lblFamRecord = iup.label { Title=" Records: " } local tglFamRecAll = iup.toggle { Title="All" } -- V2.9 local tglFamRecSel = iup.toggle { Title="Pick" } -- V2.9 local radFamRecord = iupRadio ( tglFamRecAll, tglFamRecSel ) -- V2.9 -- Create the Other Records controls with titles/values and tooltips, etc local lblRecords = iup.label { Title=" Record Type:" , Alignment="ARIGHT", Size="80" } LstRecords = iup.list { DropDown="YES", Value="1" , VisibleColumns="9", Visible_Items="10" } local lblRecOldSet = iup.label { Title=" Source Tag Set:" , Alignment="ARIGHT", Size="80" } LstRecOldSet = iup.list { DropDown="YES", Sort="NO" , VisibleColumns="9", Visible_Items="16" } local lblRecNewSet = iup.label { Title=" Target Tag Set:" , Alignment="ARIGHT", Size="80" } LstRecNewSet = iup.list { DropDown="YES", Sort="NO" , VisibleColumns="9", Visible_Items="16" } local lblRecOldTag = iup.label { Title=" Source Tag Name:", Alignment="ARIGHT", Size="80" } LstRecOldTag = iup.list { DropDown="YES", Sort="YES", VisibleColumns="9", Visible_Items="16" } local lblRecNewTag = iup.label { Title=" Target Tag Name:", Alignment="ARIGHT", Size="80" } LstRecNewTag = iup.list { DropDown="YES", Sort="YES", VisibleColumns="9", Visible_Items="16" } local btnRecDelete = iup.button { Title=strRecDelete, Active="NO" } local btnRecChange = iup.button { Title=strRecChange, Active="NO" } local lblRecValue = iup.label { Title=" Value:" } local txtRecValue = iup.text { Value=tblRecFilter[IntFilterValue] } local lblRecDate = iup.label { Title=" Date:" } local txtRecDate = iup.text { Value=tblRecFilter[IntFilterDate] } local lblRecPlace = iup.label { Title=" Place:" } local txtRecPlace = iup.text { Value=tblRecFilter[IntFilterPlace] } local lblRecRecord = iup.label { Title=" Records: " } local tglRecRecAll = iup.toggle { Title="All" } -- V2.9 local tglRecRecSel = iup.toggle { Title="Pick" } -- V2.9 local radRecRecord = iupRadio ( tglRecRecAll, tglRecRecSel ) -- V2.9 -- Create the Status & Settings controls with titles/values and tooltips, etc local lblCounts = iup.label { Title=" Data Counts: ", Alignment="ARIGHT" } LblCoSkipped = iup.label { Title=" 0 Skipped. ", Alignment="ARIGHT" } LblCoDeleted = iup.label { Title=" 0 Deleted. ", Alignment="ARIGHT" } LblCoChanged = iup.label { Title=" 0 Changed. ", Alignment="ARIGHT:ABOTTOM" } LblCoWarning = iup.label { Title=" 0 Warnings.", Alignment="ARIGHT" } local lblTotals = iup.label { Title=" Data Totals: ", Alignment="ARIGHT" } LblToSkipped = iup.label { Title=" 0 Skipped. ", Alignment="ARIGHT" } LblToDeleted = iup.label { Title=" 0 Deleted. ", Alignment="ARIGHT" } LblToChanged = iup.label { Title=" 0 Changed. ", Alignment="ARIGHT" } LblToWarning = iup.label { Title=" 0 Warnings.", Alignment="ARIGHT" } local tglLogNeed = iup.toggle { Title=StrLogNeedTitle.." ?" } local tglPattern = iup.toggle { Title=StrPatternTitle.." ?" } local tglConfirm = iup.toggle { Title=StrConfirmTitle.." ?" } local tglWarning = iup.toggle { Title=StrWarningTitle.." ?" } LblLogFile = iup.label { Title="Log File not created.", WordWrap="YES", Alignment=":ATOP", Size="90x16" } local btnViewLog = iup.button { Title=" View && Delete Log Files" } local btnDefault = iup.button { Title="Restore Default Settings" } local btnSetFont = iup.button { Title="Set Window Fonts" } local btnQuit = iup.button { Title="QUIT Plugin and UNDO Edits" } local btnGetHelp = iup.button { Title=" Obtain Help && Advice" } local btnClose = iup.button { Title="CLOSE Plugin and KEEP Edits" } local intTabPosn = 0 -- GUI Tab currently displayed, 0=Undefined, 1=Individual, 2=Family, 3=Other, 4=Status -- Create the Individual Records box local headIndiv = iup.label { Title=" ~ Choose an Individual Record Fact/Tag to Edit ~ " } local vboxIndiv = iup.vbox { Alignment="ACENTER", Margin="0x0", headIndiv, iup.hbox { Homogeneous="YES", iup.hbox { iup.label{Size="80"},btnCHR_BAPM}, iup.hbox { iup.label{Size="80"},btnBAPM_CHR} }, iup.hbox { Homogeneous="YES", iup.hbox { lblIndOldSet, LstIndOldSet } , iup.hbox { lblIndNewSet, LstIndNewSet } }, iup.hbox { Homogeneous="YES", iup.hbox { lblIndOldTag, LstIndOldTag } , iup.hbox { lblIndNewTag, LstIndNewTag } }, iup.hbox { Homogeneous="YES", iup.hbox { btnIndDelete } , iup.hbox { btnIndChange } }, iup.hbox { Homogeneous="YES", iup.hbox { lblIndValue, txtIndValue }, iup.hbox { lblIndDate, txtIndDate }, iup.hbox { lblIndPlace, txtIndPlace }, iup.hbox { lblIndRecord, radIndRecord } }, -- V2.9 } -- Create the Family Records box local headFamily = iup.label { Title=" ~ Choose a Family Record Fact/Tag to Edit ~ " } local vboxFamily = iup.vbox { Alignment="ACENTER", Margin="0x0", headFamily, iup.hbox { Homogeneous="YES", iup.hbox { iup.label{Size="80"},btnDIV_ANUL}, iup.hbox { iup.label{Size="80"},btnANUL_DIV } }, iup.hbox { Homogeneous="YES", iup.hbox { lblFamOldSet, LstFamOldSet } , iup.hbox { lblFamNewSet, LstFamNewSet } }, iup.hbox { Homogeneous="YES", iup.hbox { lblFamOldTag, LstFamOldTag } , iup.hbox { lblFamNewTag, LstFamNewTag } }, iup.hbox { Homogeneous="YES", iup.hbox { btnFamDelete } , iup.hbox { btnFamChange } }, iup.hbox { Homogeneous="YES", iup.hbox { lblFamValue, txtFamValue }, iup.hbox { lblFamDate, txtFamDate }, iup.hbox { lblFamPlace, txtFamPlace }, iup.hbox { lblFamRecord, radFamRecord } }, -- V2.9 } -- Create the Other Records box local headRecord = iup.label { Title=" ~ Choose a Record Type Gedcom Tag to Edit ~ " } local vboxRecord = iup.vbox { Alignment="ACENTER", Margin="0x0", headRecord, iup.hbox { Homogeneous="YES", iup.hbox { lblRecords , LstRecords } , iup.hbox { } }, iup.hbox { Homogeneous="YES", iup.hbox { lblRecOldSet, LstRecOldSet } , iup.hbox { lblRecNewSet, LstRecNewSet } }, iup.hbox { Homogeneous="YES", iup.hbox { lblRecOldTag, LstRecOldTag } , iup.hbox { lblRecNewTag, LstRecNewTag } }, iup.hbox { Homogeneous="YES", iup.hbox { btnRecDelete } , iup.hbox { btnRecChange } }, iup.hbox { Homogeneous="YES", iup.hbox { lblRecValue, txtRecValue }, iup.hbox { lblRecDate, txtRecDate }, iup.hbox { lblRecPlace, txtRecPlace }, iup.hbox { lblRecRecord, radRecRecord } }, -- V2.9 } -- Create the Status box local headStatus = iup.label { Title=" ~ Status and Settings Information ~ " } HboxCounts = iup.hbox { Homogeneous="YES", lblCounts, LblCoSkipped, LblCoDeleted, LblCoChanged, LblCoWarning, iup.label { } } local hboxTotals = iup.hbox { Homogeneous="YES", lblTotals, LblToSkipped, LblToDeleted, LblToChanged, LblToWarning, iup.label {visible="NO"} } local vboxStatus = iup.vbox { Alignment="ACENTER", Margin="2x0", headStatus, iup.vbox { Homogeneous="YES", HboxCounts, hboxTotals, iup.hbox { Homogeneous="YES", tglLogNeed, tglPattern, tglConfirm, tglWarning }, }, iup.hbox { Homogeneous="YES", LblLogFile }, iup.hbox { Homogeneous="YES", btnViewLog, btnDefault, btnSetFont, Margin="0x0" }, } -- Create the Tab controls local tabControls = iup.tabs { vboxIndiv, TabTitle0="Individual Records", vboxFamily, TabTitle1="Family Records", vboxRecord, TabTitle2="Other Records", vboxStatus, TabTitle3="Status && Settings ", tabchangepos_cb=function(self,intNewTab,intOldTab) intTabPosn=intNewTab+1 if intTabPosn <= 3 then strRecTag=StrPopulateDropdownLists(TblRecordTag[intTabPosn]) end end, -- 17 Aug 2013 -- 28 Dec 2013 } -- Create dialogue and turn off resize, menubox, maximize and minimize local headMain = iup.label { Alignment="ACENTER:ACENTER", Title="Please only use this plugin on a copy of your data until you are satisfied with the results." } local vboxMain = iup.vbox { Alignment="ACENTER", Gap=iup_gui.Gap, Margin=iup_gui.Margin, headMain, tabControls, iup.hbox { Homogeneous="YES", btnQuit, btnGetHelp, btnClose }, } local dialogMain = iup.dialog { Title=Utf8PluginTitle..iup_gui.Version, vboxMain, close_cb=function() if vboxMain.Active == "NO" then return iup.IGNORE else return iup.CLOSE end end, } if fhGetAppVersion() > 6 then -- FH V7 IUP 3.28 -- V3.1 tabControls.TabPadding="10x7" else -- FH V6 IUP 3.11 -- V3.1 tabControls.Padding="10x7" end local function doChangeData(...) -- Disable GUI while changing data vboxMain.Active = "NO" DoChangeData(...) vboxMain.Active = "YES" end -- local function doChangeData local function setControls() -- Set GUI control values tglLogNeed.Value = StrLogNeed tglPattern.Value = StrPattern tglConfirm.Value = StrConfirm tglWarning.Value = StrWarning tblIndFilter[IntFilterRecord] = {} -- Reset the Records filters -- V2.9 tglIndRecAll.Value = "ON" tblFamFilter[IntFilterRecord] = {} tglFamRecAll.Value = "ON" tblRecFilter[IntFilterRecord] = {} tglRecRecAll.Value = "ON" end -- local function setControls local function setToolTip(iupItem) -- Refresh control tooltip otherwise it vanishes in XP! iupItem.Tip = iupItem.Tip end -- local function setToolTip local strTipDelete = "Delete selected Source Facts/Tags" local strTipChange = "Change selected Source Facts/Tags to Target Facts/Tags" local strTipOldSet = "Select source Fact/Tag Set for the list below" local strTipNewSet = "Select target Fact/Tag Set for the list below" local strTipOldTag = "Select source Fact/Tag Name from the Set above" local strTipNewTag = "Select target Fact/Tag Name from the Set above" local strTipValue = "Filter source on Attribute Value" local strTipDate = "Filter source on Fact/Tag Date" local strTipPlace = "Filter source on Fact/Tag Place" local strTipRecord = "Filter source by Records ?" -- V2.9 local strTipRecAll = "Do not filter source by Records" -- V2.9 local strTipRecSel = "Filter source by selected Records" -- V2.9 local tblState = { "OFF", "ON" } -- Toggle control settings per state -- Set other GUI control attributes, but see later for further action functions local tblControls={{ "Font", "FgColor", "Expand", "Tip", "action", {"TipBalloon";"Balloon"}, setControls }, -- Individual Records [headIndiv] = { "FontHead", "Head", "VERTICAL", }, [vboxIndiv] = { "FontBody", "Body", false, }, [btnCHR_BAPM] = { "FontBody", "Safe", "YES", "Change Christening to Baptism Events", function() doChangeData("INDI",StrStandard,StrStandard,"CHR","BAPM",tblIndFilter) end }, [btnBAPM_CHR] = { "FontBody", "Safe", "YES", "Change Baptism to Christening Events", function() doChangeData("INDI",StrStandard,StrStandard,"BAPM","CHR",tblIndFilter) end }, [lblIndOldSet]= { "FontBody", "Body", false, strTipOldSet }, [LstIndOldSet]= { "FontBody", "Safe", "YES", strTipOldSet }, [lblIndNewSet]= { "FontBody", "Body", false, strTipNewSet }, [LstIndNewSet]= { "FontBody", "Safe", "YES", strTipNewSet }, [lblIndOldTag]= { "FontBody", "Body", false, strTipOldTag }, [LstIndOldTag]= { "FontBody", "Safe", "YES", strTipOldTag }, [lblIndNewTag]= { "FontBody", "Body", false, strTipNewTag }, [LstIndNewTag]= { "FontBody", "Safe", "YES", strTipNewTag }, [btnIndDelete]= { "FontBody", "Safe", "YES", strTipDelete }, [btnIndChange]= { "FontBody", "Safe", "YES", strTipChange }, [lblIndValue] = { "FontBody", "Body", false, strTipValue }, [txtIndValue] = { "FontBody", "Safe", "YES", strTipValue }, [lblIndDate] = { "FontBody", "Body", false, strTipDate }, [txtIndDate] = { "FontBody", "Safe", "YES", strTipDate }, [lblIndPlace] = { "FontBody", "Body", false, strTipPlace }, [txtIndPlace] = { "FontBody", "Safe", "YES", strTipPlace }, [lblIndRecord]= { "FontBody", "Body", false, strTipRecord }, -- V2.9 [tglIndRecAll]= { "FontBody", "Body", "YES", strTipRecAll }, -- V2.9 [tglIndRecSel]= { "FontBody", "Body", "YES", strTipRecSel }, -- V2.9 -- Family Records [headFamily] = { "FontHead", "Head", "VERTICAL", }, [vboxFamily] = { "FontBody", "Body", false, }, [btnDIV_ANUL] = { "FontBody", "Safe", "YES", "Change Divorce to Annulment Events", function() doChangeData("FAM",StrStandard,StrStandard,"DIV","ANUL",tblFamFilter) end }, [btnANUL_DIV] = { "FontBody", "Safe", "YES", "Change Annulment to Divorce Events", function() doChangeData("FAM",StrStandard,StrStandard,"ANUL","DIV",tblFamFilter) end }, [lblFamOldSet]= { "FontBody", "Body", false, strTipOldSet }, [LstFamOldSet]= { "FontBody", "Safe", "YES", strTipOldSet }, [lblFamNewSet]= { "FontBody", "Body", false, strTipNewSet }, [LstFamNewSet]= { "FontBody", "Safe", "YES", strTipNewSet }, [lblFamOldTag]= { "FontBody", "Body", false, strTipOldTag }, [LstFamOldTag]= { "FontBody", "Safe", "YES", strTipOldTag }, [lblFamNewTag]= { "FontBody", "Body", false, strTipNewTag }, [LstFamNewTag]= { "FontBody", "Safe", "YES", strTipNewTag }, [btnFamDelete]= { "FontBody", "Safe", "YES", strTipDelete }, [btnFamChange]= { "FontBody", "Safe", "YES", strTipChange }, [lblFamValue] = { "FontBody", "Body", false, strTipValue }, [txtFamValue] = { "FontBody", "Safe", "YES", strTipValue }, [lblFamDate] = { "FontBody", "Body", false, strTipDate }, [txtFamDate] = { "FontBody", "Safe", "YES", strTipDate }, [lblFamPlace] = { "FontBody", "Body", false, strTipPlace }, [txtFamPlace] = { "FontBody", "Safe", "YES", strTipPlace }, [lblFamRecord]= { "FontBody", "Body", false, strTipRecord }, -- V2.9 [tglFamRecAll]= { "FontBody", "Body", "YES", strTipRecAll }, -- V2.9 [tglFamRecSel]= { "FontBody", "Body", "YES", strTipRecSel }, -- V2.9 -- Other Records [headRecord] = { "FontHead", "Head", "VERTICAL", }, [vboxRecord] = { "FontBody", "Body", false, }, [lblRecords] = { "FontBody", "Body", false, "Select record type" }, [LstRecords] = { "FontBody", "Safe", "YES", "Select record type" }, [lblRecOldSet]= { "FontBody", "Body", false, strTipOldSet:gsub("Fact/","") }, [LstRecOldSet]= { "FontBody", "Safe", "YES", strTipOldSet:gsub("Fact/","") }, [lblRecNewSet]= { "FontBody", "Body", false, strTipNewSet:gsub("Fact/","") }, [LstRecNewSet]= { "FontBody", "Safe", "YES", strTipNewSet:gsub("Fact/","") }, [lblRecOldTag]= { "FontBody", "Body", false, strTipOldTag:gsub("Fact/","") }, [LstRecOldTag]= { "FontBody", "Safe", "YES", strTipOldTag:gsub("Fact/","") }, [lblRecNewTag]= { "FontBody", "Body", false, strTipNewTag:gsub("Fact/","") }, [LstRecNewTag]= { "FontBody", "Safe", "YES", strTipNewTag:gsub("Fact/","") }, [btnRecDelete]= { "FontBody", "Safe", "YES", strTipDelete:gsub("Facts/","") }, [btnRecChange]= { "FontBody", "Safe", "YES", strTipChange:gsub("Facts/","") }, [lblRecValue] = { "FontBody", "Body", false, strTipValue:gsub("Attribute","Tag") }, [txtRecValue] = { "FontBody", "Safe", "YES", strTipValue:gsub("Attribute","Tag") }, [lblRecDate] = { "FontBody", "Body", false, strTipDate :gsub("Fact/","") }, [txtRecDate] = { "FontBody", "Safe", "YES", strTipDate :gsub("Fact/","") }, [lblRecPlace] = { "FontBody", "Body", false, strTipPlace:gsub("Fact/","") }, [txtRecPlace] = { "FontBody", "Safe", "YES", strTipPlace:gsub("Fact/","") }, [lblRecRecord]= { "FontBody", "Body", false, strTipRecord }, -- V2.9 [tglRecRecAll]= { "FontBody", "Body", "YES", strTipRecAll }, -- V2.9 [tglRecRecSel]= { "FontBody", "Body", "YES", strTipRecSel }, -- V2.9 -- Status & Settings [headStatus] = { "FontHead", "Head", "VERTICAL", }, [vboxStatus] = { "FontBody", "Body", false, }, [lblCounts] = { "FontBody", "Body", "YES", "Counts for the last edit" }, [LblCoSkipped]= { "FontBody", "Body", "YES", "Number of Facts/Tags skipped by last edit" }, [LblCoDeleted]= { "FontBody", "Body", "YES", "Number of Facts/Tags deleted by last edit" }, [LblCoChanged]= { "FontBody", "Body", "YES", "Number of Facts/Tags changed by last edit" }, [LblCoWarning]= { "FontBody", "Body", "YES", "Number of Fact/Tag warnings for last edit" }, [lblTotals] = { "FontBody", "Body", "YES", "Totals for this session" }, [LblToSkipped]= { "FontBody", "Body", "YES", "Number of Facts/Tags skipped this session" }, [LblToDeleted]= { "FontBody", "Body", "YES", "Number of Facts/Tags deleted this session" }, [LblToChanged]= { "FontBody", "Body", "YES", "Number of Facts/Tags changed this session" }, [LblToWarning]= { "FontBody", "Body", "YES", "Number of Fact/Tag warnings this session" }, [tglLogNeed] = { "FontBody", "Body", "YES", "Choose whether to log edits to file" , function(self,intState) StrLogNeed = tblState[intState+1] SaveSettings() setToolTip(self) end }, -- Refresh XP Tooltips -- V2.4 [tglPattern] = { "FontBody", "Body", "YES", "Choose to Filter with Regular Expression style Patterns, or Plain Text" , function(self,intState) StrPattern = tblState[intState+1] SaveSettings() setToolTip(self) end }, [tglConfirm] = { "FontBody", "Body", "YES", "Choose whether to confirm Fact/Tag edits" , function(self,intState) StrConfirm = tblState[intState+1] SaveSettings() setToolTip(self) end }, [tglWarning] = { "FontBody", "Body", "YES", "Choose whether to show warning messages" , function(self,intState) StrWarning = tblState[intState+1] SaveSettings() setToolTip(self) end }, [LblLogFile] = { "FontBody", "Body", "YES", "Log file name (when created)" }, [btnViewLog] = { "FontBody", "Safe", "YES", "View and optionally delete all log files" }, [btnDefault] = { "FontBody", "Safe", "YES", "Restore default Settings for Window sizes and positions, \n"..StrLogNeedTitle..", "..StrPatternTitle..", "..StrConfirmTitle..", and "..StrWarningTitle }, [btnSetFont] = { "FontBody", "Safe", "YES", "Choose user interface window font styles" }, -- Main Controls [headMain] = { "FontHead", "Risk", "VERTICAL", }, [vboxMain] = { "FontHead", "Head", false, }, [tabControls] = { "FontHead", "Head", false, "Choose between Individual Records, Family Records, or \nOther Records such as Note, Source, Repository, etc, \nor Status and Settings Information." }, [btnQuit] = { "FontBody", "Risk", "YES", "Quit the plugin and undo any edits" , function() flgQuitMode=true return iup.CLOSE end }, [btnGetHelp] = { "FontBody", "Safe", "YES", "Obtain online Help and Advice from the Plugin Store" }, [btnClose] = { "FontBody", "Risk", "YES", "Close the plugin and keep all edits" , function() return iup.CLOSE end }, } -- GUI Control Functions -- local function doUpdateButtons(strRecord,strOldTag,strNewTag,btnChange,strChange,btnDelete) if strOldTag and strNewTag and strNewTag ~= strOldTag then btnChange.Active = "YES" -- Enable & label Change button if Source & Target Tag Names OK btnChange.Title = strButtonTitle(strRecord,strOldTag," to ",strNewTag) else btnChange.Active = "NO" -- Disable & label Change button btnChange.Title = strChange end if btnDelete and strOldTag then btnDelete.Active = "YES" -- Enable & label Delete button btnDelete.Title = strButtonTitle(strRecord,nil," Delete every ",strOldTag) end end -- local function doUpdateButtons local function doIndButtonsReset(strMode) -- Reset the Delete & Change buttons and Tag Name lists -- strMode = StrModeAll if either button is pressed -- strMode = StrModeOld if 'Source Tag Set' is changed -- strMode = StrModeNew if 'Target Tag Set' is changed btnIndChange.Title = strIndChange btnIndChange.Active = "NO" if strMode == StrModeOld or strMode == StrModeAll then btnIndDelete.Title = strIndDelete btnIndDelete.Active = "NO" LstIndOldTag.Value = 0 strIndOldTag = nil end if strMode == StrModeNew or strMode == StrModeAll then LstIndNewTag.Value = 0 strIndNewTag = nil end end -- local function doIndButtonsReset local function getRecordFilter(strType) -- Prompt user for Record selection -- V2.9 local arrRec = fhPromptUserForRecordSel(strType) if #arrRec == 0 then -- Cancelled so use current selection arrRec = fhGetCurrentRecordSel(strType) end return arrRec end -- local function getRecordFilter function btnIndDelete:action() -- Action for Delete fact button doChangeData("INDI",strIndOldSet,"",strIndOldTag,StrDeleteTag,tblIndFilter) doIndButtonsReset(StrModeAll) end function btnIndChange:action() -- Action for Change facts button doChangeData("INDI",strIndOldSet,strIndNewSet,strIndOldTag,strIndNewTag,tblIndFilter) doIndButtonsReset(StrModeAll) end function LstIndOldSet:action(strText,intItem,iState) -- Action for Source Tag Set dropdown if iState == 1 then setToolTip(LstIndOldSet) -- Refresh XP Tooltip -- V2.4 LstIndOldSet.Value = intItem local indoldsetval = LstIndOldSet.Value -- Populate Source Tag Name dropdown strIndOldSet = strText DoPopulateNameDropList(strRecTag,TblOldTagIndex["INDI"][strIndOldSet],LstIndOldTag) doIndButtonsReset(StrModeOld) end end function LstIndNewSet:action(strText,intItem,iState) -- Action for Target Tag Set dropdown if iState == 1 then setToolTip(LstIndNewSet) -- Refresh XP Tooltip -- V2.4 LstIndNewSet.Value = intItem local indnewsetval = LstIndNewSet.Value -- Populate Target Tag Name dropdown strIndNewSet = strText DoPopulateNameDropList(strRecTag,TblNewTagIndex["INDI"][strIndNewSet],LstIndNewTag) doIndButtonsReset(StrModeNew) end end function LstIndOldTag:action(strText,intItem,iState) -- Action for Source Tag Name dropdown if iState == 1 then setToolTip(LstIndOldTag) -- Refresh XP Tooltip -- V2.4 strIndOldTag = TblDictionary["INDI"][strIndOldSet][strText] -- V2.5 doUpdateButtons("INDI",strIndOldTag,strIndNewTag,btnIndChange,strIndChange,btnIndDelete) end end function LstIndNewTag:action(strText,intItem,iState) -- Action for Target Tag Name dropdown if iState == 1 then setToolTip(LstIndNewTag) -- Refresh XP Tooltip -- V2.4 strIndNewTag = TblDictionary["INDI"][strIndNewSet][strText] -- V2.5 doUpdateButtons("INDI",strIndOldTag,strIndNewTag,btnIndChange,strIndChange) end end function txtIndValue:valuechanged_cb() -- Call back when Filter Value is changed tblIndFilter[IntFilterValue] = txtIndValue.Value end function txtIndDate:valuechanged_cb() -- Call back when Filter Date is changed tblIndFilter[IntFilterDate] = txtIndDate.Value end function txtIndPlace:valuechanged_cb() -- Call back when Filter Place is changed tblIndFilter[IntFilterPlace] = txtIndPlace.Value end function tglIndRecAll:action(intState) -- Action for Record All toggle -- V2.9 if intState == 1 then setToolTip(tglIndRecAll) -- Refresh XP Tooltip tblIndFilter[IntFilterRecord] = {} end end function tglIndRecSel:action(intState) -- Action for Record Pick toggle -- V2.9 if intState == 1 then setToolTip(tglIndRecSel) -- Refresh XP Tooltip tblIndFilter[IntFilterRecord] = getRecordFilter("INDI") --[=[ tblIndFilter[IntFilterRecord] = fhPromptUserForRecordSel("INDI") if #tblIndFilter[IntFilterRecord] == 0 then -- Prompt cancelled so use current selection tblIndFilter[IntFilterRecord] = fhGetCurrentRecordSel("INDI") end --]=] end end local function doFamButtonsReset(strMode) -- Reset the Delete & Change buttons and Tag Name lists -- strMode = StrModeAll if either button is used -- strMode = StrModeOld if 'Source Tag Set' is changed -- strMode = StrModeNew if 'Target Tag Set' is changed btnFamChange.Title = strFamChange btnFamChange.Active = "NO" if strMode == StrModeOld or strMode == StrModeAll then btnFamDelete.Title = strFamDelete btnFamDelete.Active = "NO" LstFamOldTag.Value = 0 strFamOldTag = nil end if strMode == StrModeNew or strMode == StrModeAll then LstFamNewTag.Value = 0 strFamNewTag = nil end end -- local function doFamButtonsReset function btnFamDelete:action() -- Action for Delete fact button doChangeData("FAM",strFamOldSet,"",strFamOldTag,StrDeleteTag,tblFamFilter) doFamButtonsReset(StrModeAll) end function btnFamChange:action() -- Action for Change facts button doChangeData("FAM",strFamOldSet,strFamNewSet,strFamOldTag,strFamNewTag,tblFamFilter) doFamButtonsReset(StrModeAll) end function LstFamOldSet:action(strText,intItem,iState) -- Action for Source Tag Set dropdown if iState == 1 then setToolTip(LstFamOldSet) -- Refresh XP Tooltip -- V2.4 strFamOldSet = strText -- Populate Source Tag Name dropdown DoPopulateNameDropList(strRecTag,TblOldTagIndex["FAM"][strFamOldSet],LstFamOldTag) doFamButtonsReset(StrModeOld) end end function LstFamNewSet:action(strText,intItem,iState) -- Action for Target Tag Set dropdown if iState == 1 then setToolTip(LstFamNewSet) -- Refresh XP Tooltip -- V2.4 strFamNewSet = strText -- Populate Target Tag Name dropdown DoPopulateNameDropList(strRecTag,TblNewTagIndex["FAM"][strFamNewSet],LstFamNewTag) doFamButtonsReset(StrModeNew) end end function LstFamOldTag:action(strText,intItem,iState) -- Action for Source Tag Name dropdown if iState == 1 then setToolTip(LstFamOldTag) -- Refresh XP Tooltip -- V2.4 strFamOldTag = TblDictionary["FAM"][strFamOldSet][strText] -- V2.5 doUpdateButtons("FAM",strFamOldTag,strFamNewTag,btnFamChange,strFamChange,btnFamDelete) end end function LstFamNewTag:action(strText,intItem,iState) -- Action for Target Tag Name dropdown if iState == 1 then setToolTip(LstFamNewTag) -- Refresh XP Tooltip -- V2.4 strFamNewTag = TblDictionary["FAM"][strFamNewSet][strText] -- V2.5 doUpdateButtons("FAM",strFamOldTag,strFamNewTag,btnFamChange,strFamChange) end end function txtFamValue:valuechanged_cb() -- Call back when Filter Value is changed tblFamFilter[IntFilterValue] = txtFamValue.Value end function txtFamDate:valuechanged_cb() -- Call back when Filter Date is changed tblFamFilter[IntFilterDate] = txtFamDate.Value end function txtFamPlace:valuechanged_cb() -- Call back when Filter Place is changed tblFamFilter[IntFilterPlace] = txtFamPlace.Value end function tglFamRecAll:action(intState) -- Action for Record All toggle -- V2.9 if intState == 1 then setToolTip(tglFamRecAll) -- Refresh XP Tooltip tblFamFilter[IntFilterRecord] = {} end end function tglFamRecSel:action(intState) -- Action for Record Pick toggle -- V2.9 if intState == 1 then setToolTip(tglFamRecSel) -- Refresh XP Tooltip tblFamFilter[IntFilterRecord] = getRecordFilter("FAM") end end local function doRecButtonsReset(strMode) -- Reset the Delete & Change buttons and Tag Name lists -- strMode = StrModeAll if either button is used -- strMode = StrModeOld if 'Source Tag Set' is changed -- strMode = StrModeNew if 'Target Tag Set' is changed btnRecChange.Title = strRecChange btnRecChange.Active = "NO" if strMode == StrModeOld or strMode == StrModeAll then btnRecDelete.Title = strRecDelete btnRecDelete.Active = "NO" LstRecOldTag.Value = 0 strRecOldTag = nil end if strMode == StrModeNew or strMode == StrModeAll then LstRecNewTag.Value = 0 strRecNewTag = nil end end -- local function doRecButtonsReset function LstRecords:action(strText,intItem,iState) -- Action for Record Type dropdown if iState == 1 then setToolTip(LstRecords) -- Refresh XP Tooltip -- V2.4 strRecTag = TblDictionary[strText] -- Populate Source/Target Tag Set dropdown lists doRecButtonsReset(StrModeAll) tblRecFilter[IntFilterRecord] = {} -- Reset the Records filter -- V2.9 tglRecRecAll.Value = "ON" strRecOldSet,strRecNewSet = StrStrPopulateSetDropList(strRecTag,LstRecOldSet,LstRecNewSet) DoPopulateNameDropList(strRecTag,TblOldTagIndex[strRecTag][strRecOldSet],LstRecOldTag) DoPopulateNameDropList(strRecTag,TblNewTagIndex[strRecTag][strRecNewSet],LstRecNewTag) end end function btnRecDelete:action() -- Action for Delete tag button doChangeData(strRecTag,strRecOldSet,"",strRecOldTag,StrDeleteTag,tblRecFilter) doRecButtonsReset(StrModeAll) end function btnRecChange:action() -- Action for Change tag button doChangeData(strRecTag,strRecOldSet,strRecNewSet,strRecOldTag,strRecNewTag,tblRecFilter) doRecButtonsReset(StrModeAll) end function LstRecOldSet:action(strText,intItem,iState) -- Action for Source Tag Set dropdown if iState == 1 then setToolTip(LstRecOldSet) -- Refresh XP Tooltip -- V2.4 strRecOldSet = strText -- Populate Source Tag Name dropdown doRecButtonsReset(StrModeOld) DoPopulateNameDropList(strRecTag,TblOldTagIndex[strRecTag][strRecOldSet],LstRecOldTag) end end function LstRecNewSet:action(strText,intItem,iState) -- Action for Target Tag Set dropdown if iState == 1 then setToolTip(LstRecNewSet) -- Refresh XP Tooltip -- V2.4 strRecNewSet = strText -- Populate Target Tag Name dropdown doRecButtonsReset(StrModeNew) DoPopulateNameDropList(strRecTag,TblNewTagIndex[strRecTag][strRecNewSet],LstRecNewTag) end end function LstRecOldTag:action(strText,intItem,iState) -- Action for Source Tag Name dropdown if iState == 1 then setToolTip(LstRecOldTag) -- Refresh XP Tooltip -- V2.4 strRecOldTag = TblDictionary[strRecTag][strRecOldSet][strText] -- V2.5 doUpdateButtons(strRecTag,strRecOldTag,strRecNewTag,btnRecChange,strRecChange,btnRecDelete) end end function LstRecNewTag:action(strText,intItem,iState) -- Action for Target Tag Name dropdown if iState == 1 then setToolTip(LstRecNewTag) -- Refresh XP Tooltip -- V2.4 strRecNewTag = TblDictionary[strRecTag][strRecNewSet][strText] -- V2.5 doUpdateButtons(strRecTag,strRecOldTag,strRecNewTag,btnRecChange,strRecChange) end end function txtRecValue:valuechanged_cb() -- Call back when Filter Value is changed tblRecFilter[IntFilterValue] = txtRecValue.Value end function txtRecDate:valuechanged_cb() -- Call back when Filter Date is changed tblRecFilter[IntFilterDate] = txtRecDate.Value end function txtRecPlace:valuechanged_cb() -- Call back when Filter Place is changed tblRecFilter[IntFilterPlace] = txtRecPlace.Value end function tglRecRecAll:action(intState) -- Action for Record All toggle -- V2.9 if intState == 1 then setToolTip(tglRecRecAll) -- Refresh XP Tooltip tblRecFilter[IntFilterRecord] = {} end end function tglRecRecSel:action(intState) -- Action for Record Pick toggle -- V2.9 if intState == 1 then setToolTip(tglRecRecSel) -- Refresh XP Tooltip tblRecFilter[IntFilterRecord] = getRecordFilter(strRecTag) end end function btnViewLog:action() -- Action for View & Delete Log Files button local tblLogFile = {} -- Table of log file names tblLogFile[0] = 0 -- Count of log file names for strFile, tblAttr in general.DirTree(AnsiProjectPath) do if string.find(strFile,AnsiProjectPath.."\\"..AnsiPluginTitle.." - ",1,true) ~= nil then -- Add log file name to table tblLogFile[0] = tblLogFile[0] + 1 tblLogFile[tblLogFile[0]] = strFile end end GUI_LogsDialogue(tblLogFile) -- Display log file popup end function btnDefault:action() -- Action for Restore Defaults button ResetDefaultSettings() setControls() iup_gui.ShowDialogue("Main") SaveSettings() -- Save sticky data settings end -- function btnDefault:action function btnSetFont:action() -- Action for User Interface Font button btnSetFont.Active = "NO" iup_gui.FontDialogue(tblControls) SaveSettings() -- Save sticky data settings btnSetFont.Active = "YES" end -- function btnSetFont:action function btnSetFont:button_cb(intButton,intPress) -- Action for mouse right-click on Set Window Fonts button if intButton == iup.BUTTON3 and intPress == 0 then iup_gui.BalloonToggle() -- Toggle tooltips Balloon mode end end -- function btnSetFont:button_cb local function doExecute(strExecutable, strParameter) -- Invoke FH Shell Execute API -- V3.1 local function ReportError(strMessage) iup_gui.WarnDialogue( "Shell Execute Error", "ERROR: "..strMessage.." :\n"..strExecutable.."\n"..strParameter.."\n\n", "OK" ) end -- local function ReportError return general.DoExecute(strExecutable, strParameter, ReportError) end -- local function doExecute local strHelp = "https://pluginstore.family-historian.co.uk/page/help/change-any-fact-tag" local arrHelp = { "-individual-records-tab"; "-family-records-tab"; "-other-records-tab"; "-status-and-settings-tab"; } function btnGetHelp:action() -- Action for Help and Advice button -- V3.1 local strPage = arrHelp[intTabPosn] or "" doExecute( strHelp..strPage ) fhSleep(3000,500) dialogMain.BringFront="YES" end -- function btnGetHelp:action iup_gui.ShowDialogue("Main",dialogMain,btnClose,"map") -- Map the Main dialogue for dropdown lists, etc iup_gui.AssignAttributes(tblControls) -- Assign GUI control attributes strRecTag = StrPopulateDropdownLists("INDI") -- Populate the Individual tab dropdown lists -- 17 Aug 2013 if LogFileHandle then LblLogFile.Title = Utf8LogFilePath end -- Update the log filename display progbar.Setup(iup_gui.DialogueAttributes("Bars")) -- Setup the progress bar attributes iup_gui.ShowDialogue("Main") -- Show the Main dialogue if flgQuitMode then SaveSettings() -- Save sticky data settings error("\n\nQUIT Plugin and UNDO Changes.\n\n") end end -- function GUI_MainDialogue -- Main body of Plugin script starts here -- fhInitialise(5,0,2,"save_recommended") -- V5.0.2 fhCreateItem FILE/FORM update (V5.0.8 for sticky settings with scope) PresetGlobalData() -- Preset global data definitions ResetDefaultSettings() -- Preset default sticky settings LoadSettings() -- Load sticky data settings iup_gui.CheckVersionInStore() -- Notify if later Version available LoadFactsAndTags() -- Load all defined Fact Sets, undefined Custom Facts, Gedcom Defined Tags, and UDF Tag Sets GUI_MainDialogue() -- Invoke main dialogue SaveSettings() -- Save sticky data settings if #TblPtrName > 0 then -- Create Query Result Set fhOutputResultSetColumn("Record Name", "item", TblPtrName, #TblPtrName, 120, "align_left") fhOutputResultSetColumn("Old Item" , "text", TblOldItem, #TblPtrName, 060, "align_left") fhOutputResultSetColumn("Old Data" , "text", TblOldData, #TblPtrName, 200, "align_left") fhOutputResultSetColumn("Action" , "text", TblPerform, #TblPtrName, 030, "align_left") fhOutputResultSetColumn("New Item" , "text", TblNewItem, #TblPtrName, 060, "align_left") fhOutputResultSetColumn("Link Data", "item", TblPtrData, #TblPtrName, 200, "align_left") fhOutputResultSetTitles("Results Log") end

Source:Change-Any-Fact-Tag-3.fh_lua