Search and Replace.fh_lua

--[[
@Title:			Search and Replace
@Type:				Standard
@Author:			Mike Tate
@Contributors:	
@Version:			3.4
@Keywords:		
@LastUpdated:	19 Aug 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:	Searches for and replaces any given text or date within the current project, with an option to ask for confirmation for each change.
					At the end, lists all changed fields with links back to the record (double-click on an item to view it in the Property Box).
@TBD:				Check replacement success/failure for duplicate Place names, etc; New option to support files;
@V3.4:				Add collectgarbage() memory management; Fix for Data Class of Metafields;
@V3.3:				Correct updating of Rich Text involving object links;
@V3.2:				Replace Place records first to avoid new records in DoAllItems(); Preserve rich text record links in doManageString(...); Minor updates to Major Options tab dialogue;
@V3.1:				Updated library to Functions Prototypes v3.0; iup_gui.Balloon = "NO" for PlayOnLinux/Mac; Show/Hide white space in Result Set; Clear Result Set if no data; Prompt GUI taller wordwrap boxes; FH V7 Lua 3.5 IUP 3.28;
@V3.0:				Libray V2.9, Show/Hide white space chars as in Word, rearrange Search Scope, Basic Filters, Search Criteria on Major Options tab, add LMO/Sort Date (~OBJE._DATE).
@V2.9:				When Media Linked File is Replaced then similarly alter Media file path itself, include FILE tag previously omitted, and cater for both OBJE.FORM and PLAC.FORM tags.
@V2.8:				Fix StrSP where [ needs no % escape, new Select Records Search Scope filter, and updated library modules.
@V2.7:				Extra format checks and completion report for Import Preset Files, and other minor improvements.
@V2.6:				Initialisation progress bars for large Projects, add multi-selection and Import/Export Presets to Manage Presets dialogue.
@V2.5:				Support named local Project Presets and global Program Presets, revised tblFilters titles & tooltips, and change for strDataClass == "date" in DoSeekMatch().
@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 uses NaturalSize,
@V2.4:				iup_gui update, fix test for newline in Search/Replace, add Place records & Witness Role fields, update ToolTips for ShortText, replace CP1252  with ASCII ~ to allow both ANSI & UTF-8 encoding.
@V2.3:				Date Phrase Warnings inactive when Date unticked, add BalloonToggle().
@V2.2:				iup_gui update to fix positioning of Confirmation popup after Restore Defaults, fix for interpreted Date Phrases, toggle for Date Phrase Warnings, and other minor fixes.
@V2.1:				iup_gui update, comments removed, Attribute values & other dropdown lists, support tab & newline, inhibit Whole Words for spaces/punctuation, allow repeated words, fix XP tooltips, etc.
@V2.0:				Better Item Details, Attribute values, more field search options, Search Only mode, updated GUI, Help & Advice, Sticky Settings, Libraries, etc.
@V1.7:				Add the Date and specific Tag related field search & replace capability (see lines marked with -- V1.7) plus major code tidy.
@V1.6:				Add more details to the replace prompt for the context of the field being changed.
@V1.5:				General code tidy (not published).
@V1.4:				Add options for whole word search only and prevent wild card patterns being used automatically 
@V1.3:				Add Case insensitive searching and cancel or close the window will abort the replaces, while still listing the changes made.
@V1.2:				Change to Provide a Skip Button, to cancel click the close button on the window.
@V1.1:				Add Skip tick box to replace prompt.
--]]

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

--[[
@Title:			aa Library Functions Preamble
@Author:			Mike Tate
@Version:			3.1
@LastUpdated:	22 Jun 2021
@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.9 @LastUpdated: 22 Jun 2021 @Description: Graphical User Interface Library Module @V3.9: ShowDialogue() popup closure fhSleep() added; CheckVersionInStore() at monthly intervals; @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 fhSleep(200,150) -- Sometimes needed to prevent MainLoop() closure! -- V3.9 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(strPlugin) -- Obtain the Version in Plugin Store by Name only -- V3.9 local strVersion = "0" if strPlugin then local strFile = fh.MachinePath.."\\VersionInStore "..strPlugin..".dat" local intTime = os.time() - 2600000 -- Time in seconds a month ago -- V3.9 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 -- V3.9 general.SaveStringToFile(strFile,strFile) -- Update file modified time local strFile = fh.MachinePath.."\\VersionInStoreInternetError.dat" local strRequest ="http://www.family-historian.co.uk/lnk/checkpluginversion.php?name="..strPlugin 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 else general.DeleteFile(strFile) -- Delete file if Internet is OK if strReturn ~= nil then strVersion = strReturn:match("([%d%.]*),%d*") -- Version digits & dots then comma and Id digits end end end end return strVersion or "0" 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() iup_gui.Gap = "2" iup_gui.Balloon = "NO" -- Needed for PlayOnLinux/Mac -- V3.1 iup_gui.SetUtf8Mode() StrSP = "\t-\r !-/:-@[-`{-~" -- Substitute LUA pattern for %s%p ( [ is not magic here so needs no % escape ) StrMid_Dot = "" -- Space symbol StrRtArrow = "" -- Hor Tab symbol StrPilcrow = "" -- Newline symbol if fhGetAppVersion() > 5 then StrMid_Dot = fhConvertANSItoUTF8(StrMid_Dot) -- White space UTF8 symbols -- V3.0 StrRtArrow = fhConvertANSItoUTF8(StrRtArrow) StrPilcrow = fhConvertANSItoUTF8(StrPilcrow) end DicEncode = { } -- White space encoding -- V3.0 DicEncode[" "] = StrMid_Dot DicEncode["\t"] = StrRtArrow.."\t" DicEncode["\n"] = StrPilcrow.."\n" IntCaret = -9 -- Text edit caret position -- V3.0 ArrOption = {} -- Array of global Program Preset GUI options -- V2.5 TblOption = {} -- Table of local Project Preset GUI options TblRecord = {} -- Result Set tables TblDataRef = {} TblOldValue = {} TblNewValue = {} TblRefValue = {} -- V3.1 TblResultSet= { Title="Search and Replace "; Sub="No Matching Data"; Old="Data Value"; } end -- function PresetGlobalData -- Set User Default Options -- function UserDefaultOptions(tblOption) if not tblOption.Val then tblOption.Val = {} end -- Search and Replace strings are in Val sub-table if not tblOption.Lst then tblOption.Lst = {} end -- All dropdown list settings are in Lst sub-table if not tblOption.Tgl then tblOption.Tgl = {} end -- All toggle ON/OFF settings are in Tgl sub-table tblOption.Val.Seek = "" -- Search text string empty tblOption.Val.Swap = "" -- Replace text string empty for strName, anyValue in pairs (TblOption.Lst) do tblOption.Lst[strName] = "" -- Reset all GUI dropdown lists associated with toggles end for strName, anyValue in pairs (TblOption.Tgl) do tblOption.Tgl[strName] = "ON" -- Enable all GUI toggle options except as below end tblOption.Tgl.Conf = "ON" -- V2.1 Ensure Confirmation toggle is ticked tblOption.Tgl["SOUR.TITL"]= "ON" -- "OFF" -- V2.1 Parent Child Tag for Source Titles -- V1.7 tblOption.Tgl.age = "ON" -- "OFF" -- V2.1 Class age is just the Age fields -- V1.7 tblOption.Tgl.Age = "OFF" -- V2.1 Age field error checks tblOption.Tgl.date = "OFF" -- V1.7 Class date is all the Date fields tblOption.Tgl.Date = "OFF" -- V1.7 Date field warnings tblOption.Tgl.Phrase = "ON" -- V2.2 Date Phrase warnings end -- function UserDefaultOptions -- 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("Preset","0x0") -- Custom "Preset" dialogue minimum size & centralisation iup_gui.DefaultDialogue("Preset","Bars","Memo") -- GUI window rastersize and X & Y co-ordinates for "Main","Font","Preset","Bars","Memo" dialogues IntTabPosn = 0 -- Tab position undefined UserDefaultOptions(TblOption) -- GUI Modes are mixed case, Classes are lower case, Tags are upper case if not TblOption.Set then TblOption.Set = {} end -- Non-toggle option settings are in Set sub-table TblOption.Set.Item = 0 -- Item count of database size TblOption.Set.Date = "" -- Date of last Item update TblOption.Set.Recs = nil -- Record Ids Search Scope filter off -- V2.8 local intCurr = tonumber(TblOption.Set.Curr) or 1 TblOption[intCurr] = {} -- Clear current Project Preset settings -- V2.5 if TblOption.Set.List then TblOption.Set.List[intCurr].Lock = "OFF" end IsMemoFail = true -- V3.4 end -- function ResetDefaultSettings -- Load Sticky Settings from File -- function LoadSettings() iup_gui.LoadSettings("Preset","Bars") -- Includes "Main","Font" dialogues and "FontSet" & "History" ArrOption = iup_gui.LoadGlobal("Option",ArrOption,"Machine") -- V2.5 TblOption = iup_gui.LoadGlobal("Option",TblOption) if not ArrOption.Set then ArrOption.Set = {} end if not ArrOption.Set.List then ArrOption.Set.List = {} end -- V2.5 for intList = 1, #ArrOption.Set.List do if not ArrOption[intList] then ArrOption[intList] = {} end -- V2.5 Ensure each Program Preset entry exists end if not TblOption.Set then TblOption.Set = {} end -- Cope with legacy settings if not TblOption.Val then TblOption.Val = {} end if not TblOption.Lst then TblOption.Lst = {} end if TblOption.Set.Lock then TblOption.Set.Lock = nil end -- V2.5 local arrList = {} if not TblOption.Set.List then TblOption.Set.List = {} TblOption.Set.Curr = 1 arrList = { "Alpha"; "Bravo"; "Charlie"; "Delta"; } -- Table of local Project Preset names -- V2.5 elseif type(TblOption.Set.List[1]) == "string" then arrList = TblOption.Set.List -- Legacy local Project Preset list end for intList, strName in ipairs (arrList) do TblOption.Set.List[intList] = { Name=strName; Lock="OFF"; } end for intList = 1, #TblOption.Set.List do if not TblOption[intList] then TblOption[intList] = {} end -- V2.5 Ensure each Project Preset entry exists if not TblOption.Set.List[intList].Lock then TblOption.Set.List[intList].Lock = "OFF" end end TblOption.Set.Curr = math.min(tonumber(TblOption.Set.Curr) or 1,#TblOption.Set.List) TblOption.Val.Seek = TblOption.Set.Seek -- V2.5 Move legacy Search & Replace strings TblOption.Val.Swap = TblOption.Set.Swap TblOption.Set.Seek = nil TblOption.Set.Swap = nil TblOption.Tgl["SOUR.TITL"]= TblOption.Tgl["SOUR.TITL"] or "ON" -- V1.7 Parent Child Tag for Source Titles TblOption.Tgl.Conf = TblOption.Tgl.Conf or "ON" -- V2.1 Ensure Confirmation toggle is ticked TblOption.Tgl.age = TblOption.Tgl.age or "ON" -- V1.7 Class age is just the Age fields TblOption.Tgl.Age = TblOption.Tgl.Age or "OFF" -- V2.1 Age field error checks TblOption.Tgl.date = TblOption.Tgl.date or "OFF" -- V1.7 Class date is all the Date fields TblOption.Tgl.Date = TblOption.Tgl.Date or "OFF" -- V1.7 Date field warnings TblOption.Tgl.Phrase = TblOption.Tgl.Phrase or "ON" -- V2.2 Date Phrase warnings SaveSettings() -- Save sticky data settings end -- function LoadSettings -- Save Sticky Settings to File -- function SaveSettings() local datToday = fhNewDate(2000) datToday:SetSimpleDate(fhCallBuiltInFunction("Today")) -- Obtain date today local strToday = datToday:GetDisplayText("ABBREV") if TblOption.Set.Date ~= strToday or not TblOption.Set.INDI then -- Different date or no record type counts -- V2.3 TblOption.Set.Date = strToday -- So count database items for progress bar -- V2.3 progbar.Setup() progbar.Start("Initialisation",fhGetRecordTypeCount()) -- Initialisation Progress Bar -- V2.6 local intItem = 0 for strType in iterate.RecordTypes() do -- Loop through all record types -- V2.3 progbar.Message("Counting "..strType.." Record Items ~ Please Wait") progbar.Step() -- Step progress bar local intType = 0 for ptrItem in iterate.Items(strType) do -- Loop through all record type items intType = intType + 1 -- Count number of record type items -- V2.3 intItem = intItem + 1 -- Count number of all database items if ( intItem % 8384 ) == 0 and progbar.Stop() then error("\n Plugin Aborted. ") end end TblOption.Set[strType] = intType -- Save count of record type items -- V2.3 end TblOption.Set.Item = intItem -- Save count of all database items -- V2.3 progbar.Close() end iup_gui.SaveGlobal("Option",TblOption) iup_gui.SaveGlobal("Option",ArrOption,"Machine") -- V2.5 iup_gui.SaveSettings("Preset","Bars") -- Includes "Main","Font" dialogues and "FontSet" & "History" end -- function SaveSettings function strHideWhiteSpace(strText,strChar) -- Remove special white space symbols, or replace with \01 byte -- V3.0 strChar = strChar or "" strText = strText:gsub(StrMid_Dot," "):gsub(StrRtArrow,strChar):gsub(StrPilcrow,strChar) return strText end -- function strHideWhiteSpace function strShowWhiteSpace(strText) -- Insert special white space symbols, after removing \01 byte -- V3.0 strText = strText:gsub("\01","") strText = strText:gsub("([ \t\n])",DicEncode) return strText end -- function strShowWhiteSpace function doChange(iupSelf) -- Handle text edits when showing white space symbols -- V3.0 local intPosn = tonumber(iupSelf.CaretPos) if not TblOption.Tgl.WhSp or TblOption.Tgl.WhSp == "OFF" then local strText = strHideWhiteSpace(iupSelf.Value,"\01") -- Replace and multi-byte symbols with \01 byte local strPrev = strText:sub(intPosn-1,intPosn-1) local strCurr = strText:sub(intPosn,intPosn) local strNext = strText:sub(intPosn+1,intPosn+1) if strCurr == "\01" or strNext == "\t" or strNext == "\n" then -- Deletion of Tab or Newline with Backspace or Delete key if strCurr ~= "\01" then intPosn = intPosn + 1 end -- Adjust caret position when Delete key deletes or symbol strText = strText:sub(1,intPosn-1)..strText:sub(intPosn+1) -- Remove either \01 byte or Tab/Newline character intPosn = intPosn - 1 -- Adjust caret position for deleted character end if strPrev ~= "\01" and (strCurr == "\t" or strCurr == "\n") then -- Insertion of Tab or Newline intPosn = intPosn + 1 -- Adjust caret position for inserted or symbol end iupSelf.Value = strShowWhiteSpace(strText) iupSelf.CaretPos = intPosn end IntCaret = intPosn end -- function doChange function doCaret(iupSelf,intPosn) -- Handle cursor moves when showing white space symbols -- V3.0 if not TblOption.Tgl.WhSp or TblOption.Tgl.WhSp == "OFF" then local strText = strHideWhiteSpace(iupSelf.Value,"\01") -- Replace and multi-byte symbols with \01 byte if strText:sub(intPosn,intPosn) == "\01" then -- Skip over special and symbols if intPosn > IntCaret then intPosn = intPosn + 1 -- Moving left to right else intPosn = intPosn - 1 -- Moving right to left end iupSelf.CaretPos = intPosn end end IntCaret = intPosn end -- function doCaret function StrAgeWarning(ptrItem,strNewAge) -- Returns warning text or nil if OK, plus the new Age text -- ptrItem: Pointer to age item -- strNewAge: New value for age item if #strNewAge > 0 and TblOption.Set.Mode == "Replace" then -- No age error checks unless Age Item exists in Replace mode local strOldAge = fhGetValueAsText(ptrItem) if TblOption.Tgl.Age then -- Age field warnings enabled? fhSetValueAsText(ptrItem,strNewAge) -- Check for invalid Age returning blank local strAgeText = fhGetValueAsText(ptrItem) fhSetValueAsText(ptrItem,strOldAge) -- Restore the original Age value if strAgeText == "" then return " ! ! BEWARE ! ! Replacement age is invalid. \n The field will be DELETED if 'Replace' is confirmed. ",strNewAge else return nil,strAgeText -- Otherwise, return nil warning with corrected Age text end end end return nil,strNewAge -- For OK values or no warning return nil, and original text end -- function StrAgeWarning function StrDateWarning(ptrItem,strNewVal) -- Returns warning text or nil if date is OK -- ptrItem: Pointer to date item -- strNewVal: New value for date item if #strNewVal > 0 and TblOption.Set.Mode == "Replace" then -- No date warnings unless Date Item exists in Replace mode local oldDate = fhGetValueAsDate(ptrItem) local newDate = fhNewDate(1999) -- For "date" value type use Date Object local strIDP = " %(\".*\"%)$" -- LUA pattern for Interpreted 'Date ("Phrase")' -- V2.2 if newDate:SetValueAsText(strNewVal:gsub(strIDP,""),false) -- Is new Date valid, allowing for Interpreted Date Phrase -- V2.2 and newDate:SetValueAsText(strNewVal,true) then if TblOption.Tgl.Date then -- Date field warnings enabled? fhSetValueAsDate(ptrItem,newDate) -- Check for invalid Date warnings local strWarning = fhCallBuiltInFunction("GetDataWarning",ptrItem,1) fhSetValueAsDate(ptrItem,oldDate) -- Restore the original Date value if strWarning ~= "" then return " ! ! BEWARE ! ! Replacement date is inconsistent. \n "..strWarning.." \n It will be set anyway if 'Replace' is confirmed. " end end elseif TblOption.Tgl.Phrase then -- Date Phrase warnings enabled? -- V2.2 if strNewVal:match(strIDP) then -- Cater for Interpreted Date Phrase and plain Date Phrase -- V2.2 return " ! ! BEWARE ! ! Replacement interpreted date is invalid. \n It will become part of the Date Phrase if 'Replace' is confirmed. " else return " ! ! BEWARE ! ! Replacement date is invalid. \n It will be set as a Date Phrase if 'Replace' is confirmed. " end end end return nil -- For OK values or no warning return nil end -- function StrDateWarning function GetDataClass(ptrItem) -- Get data class for all items including metafields -- V3.4 local dicClass = { TX = "text" ; NM = "name" ; DT = "date" ; PL = "place" ; AD = "longtext" ; RP = "link" ; EN = "enumword" ; UL = "url" ; } local strClass = fhGetDataClass(ptrItem) -- Get basic item data class if strClass == "metafield" then strClass = dicClass[ fhGetMetafieldType(ptrItem)] -- Get metafield data class end return strClass end -- function GetDataClass -- Search & Replace Prompt GUI -- function SearchReplacePrompt(ptrItem,strOldVal,strNewVal,strWarning) -- ptrItem: Pointer to data item -- strOldVal: Old value of data item -- strNewVal: New value for data item -- strWarning: Warning message for Age/Date value local function intLineCount(strText,maxLines) --[[ @Function: intLineCount @Description:Works out approximate number of lines to display a text string in a GUI text box. @Parameters: strText: String to process mandatory maxLines: Maximum height for text box, defaults to 9 lines if not provided ]] maxLines = maxLines or 9 local intLines = 1 for strLine in string.gmatch(strText.."\n","(.-)\n") do -- Count lines and increase for long wrapped lines -- V3.1 intLines = intLines + math.ceil( #strLine / 150 ) end return math.min(intLines,maxLines) end -- local function intLineCount local function strItemDetails(ptrItem) -- Returns Record Tag and [Id] plus full Data Ref, and descriptive text for each Tag level local tblDetails = {} local ptrTemp = ptrItem:Clone() local strDataRef, intRecId, strRecTag = general.BuildDataRef(ptrItem) TblOption.Set.Data = strDataRef -- Save the Data Ref for Result Set table.insert(tblDetails,strRecTag.." Record Id: ["..intRecId.."] Data Ref: "..strDataRef) while ptrTemp:IsNotNull() do local strUDF = "" if fhIsUDF(ptrTemp) then -- V2.0 adds UDF indication strWarning = " ! ! BEWARE ! ! Unrecognised Data Field (UDF)" strUDF = " {*UDF!}" end table.insert(tblDetails,2,fhGetDisplayText(ptrTemp)..strUDF) ptrTemp:MoveToParentItem(ptrTemp) end return table.concat(tblDetails,"\n") end -- local function strItemDetails local function iupMultiLineText(strVal,strRead) -- Return iup.text multiline control return iup.text { Value=strVal, ReadOnly=strRead, VisibleLines=intLineCount(strVal,6), -- Add WordWrap & taller boxes for long text -- V3.1 MultiLine="Yes"; WordWrap="Yes"; AutoHide="Yes"; Formatting="Yes"; AddFormatTag=iup.user { Bulk="Yes"; CleanOut="Yes"; }; } end -- local function iupMultiLineText local strButton = "Cancel" -- Default to value for X Close window button local function iupButtonControl(strTitle) -- Return iup.button control return iup.button { Title=strTitle; MinSize="100x40"; action=function(self) strButton=self.Title return iup.CLOSE end; } end -- local function iupButtonControl if not TblOption.Tgl.WhSp then -- V3.0 strOldVal = strShowWhiteSpace(strOldVal) strNewVal = strShowWhiteSpace(strNewVal) end local strMode = TblOption.Set.Mode -- Mode is either "Replace" or "Report" text local lblDataRef = iup.label { Title="Data Reference : "; } local txtDataRef = iupMultiLineText( strItemDetails(ptrItem), "Yes" ) local lblCurrent = iup.label { Title="Current Value : "; } local txtCurrent = iupMultiLineText( strOldVal, "Yes" ) local lblReplace = iup.label { Title="Replacement Value : "; } local txtReplace = iupMultiLineText( strNewVal, "No" ) local lblWarning = iup.label { Title=strWarning; } local tglConfirm = iup.toggle { Title="Confirm every item found"; } local btnPerform = iupButtonControl( strMode ) local btnSkipOne = iupButtonControl( "Skip" ) local btnDestroy = iupButtonControl( "Cancel" ) if strMode ~= "Replace" then -- Hide the Replacement Value controls lblReplace.Visible = "No" lblReplace.Floating = "Yes" txtReplace.Visible = "No" txtReplace.Floating = "Yes" end if not strWarning then -- Disable the Warning label control lblWarning.Active = "No" end if TblOption.Tgl.Conf then -- Set initial Confirmation toggle value tglConfirm.Value = "ON" else tglConfirm.Value = "OFF" end local tblConfirm = { ON = "Confirmation prompt for each item found"; OFF = strMode.." all matching text unconditionally"; } -- Set other GUI control attributes for iupName, tblAttr in pairs ( { -- Control = 1~FgColor ; 2~Font ; 3~Expand ; 4~Tip ; 5~action ; [lblDataRef] = { iup_gui.Head; iup_gui.FontHead ; "No" ; "Data reference details" ; }; [txtDataRef] = { iup_gui.Body; iup_gui.FontBody ; "Horizontal" ; "Data reference details" ; }; [lblCurrent] = { iup_gui.Head; iup_gui.FontHead ; "No" ; "Current text string" ; }; [txtCurrent] = { iup_gui.Body; iup_gui.FontBody ; "Horizontal" ; "Current text string" ; }; [lblReplace] = { iup_gui.Head; iup_gui.FontHead ; "No" ; "Replacement text string that can be edited" ; }; [txtReplace] = { iup_gui.Safe; iup_gui.FontBody ; "Horizontal" ; "Replacement text string that can be edited" ; }; [lblWarning] = { iup_gui.Risk; iup_gui.FontBody ; "Yes" ; "Data warning message" ; }; [tglConfirm] = { iup_gui.Safe; iup_gui.FontBody ; "Yes" ; tblConfirm[tglConfirm.Value]; function() tglConfirm.Tip = tblConfirm[tglConfirm.Value] end; }; [btnPerform] = { iup_gui.Safe; iup_gui.FontBody ; "Horizontal" ; strMode.." the data item" ; }; [btnSkipOne] = { iup_gui.Safe; iup_gui.FontBody ; "Horizontal" ; "Skip this data item" ; }; [btnDestroy] = { iup_gui.Risk; iup_gui.FontBody ; "Horizontal" ; "Cancel rest of search" ; }; } ) do iupName.FgColor = tblAttr[1] iupName.Font = tblAttr[2] iupName.Expand = tblAttr[3] iupName.Tip = tblAttr[4] iupName.TipBalloon = iup_gui.Balloon if tblAttr[5] then iupName.action = tblAttr[5] end end local boxDataRef = iup.vbox { lblDataRef; txtDataRef; } local boxCurrent = iup.vbox { lblCurrent; txtCurrent; } local boxReplace = iup.vbox { lblReplace; txtReplace; } local boxControls = iup.vbox { lblWarning; iup.hbox { tglConfirm; btnPerform; btnSkipOne; btnDestroy; Margin="3x3"; Alignment="ABOTTOM"; }; } local boxDialogue = iup.vbox { boxDataRef; boxCurrent; boxReplace; boxControls; } local dlgPerform = iup.dialog { Title=iup_gui.Plugin..iup_gui.Version; Gap=iup_gui.Gap; Margin="3x3"; boxDialogue; } local strDataClass = GetDataClass(ptrItem) -- Fix for metafields -- V3.4 function txtReplace:action(intChar,strText) -- Replacement Value is being changed if strDataClass ~= "longtext" and string.char(intChar) == "\n" then return iup.IGNORE -- Prevent short text items exceeding one line end end -- function txtReplace:action function txtReplace:valuechanged_cb() -- Replacement Value has been changed if strDataClass == "age" then -- Let spaces be added to allow yrs/mns/dys to be entered local strNewVal = strHideWhiteSpace(strNewVal) -- V3.0 local strTxtVal = strHideWhiteSpace(txtReplace.Value) -- V3.0 if strNewVal:gsub("%s","") == strTxtVal:gsub("%s","") then return end strWarning,strNewVal = StrAgeWarning(ptrItem,strTxtVal) -- Regenerate any Age Warnings and corrected Value if strTxtVal ~= strNewVal then if not TblOption.Tgl.WhSp then -- V3.0 strNewVal = strShowWhiteSpace(strNewVal) end local intCaretPos = txtReplace.CaretPos -- Maintain editing caret position and update Value txtReplace.Value = strNewVal txtReplace.CaretPos = intCaretPos end elseif strDataClass == "date" then -- Regenerate any Date Warnings strWarning = StrDateWarning(ptrItem,strHideWhiteSpace(txtReplace.Value)) -- V3.0 end lblWarning.Title = strWarning if strWarning then lblWarning.Active = "Yes" else lblWarning.Active = "No" end doChange(txtReplace) -- V3.0 end -- function txtReplace:valuechanged_cb function txtReplace:caret_cb(lin,col,pos) doCaret(txtReplace,pos) -- V3.0 end -- function txtReplace:caret_cb iup_gui.ShowDialogue("User",dlgPerform,btnPerform,"Normal Keep") -- Popup dialogue that remembers its attributes & refreshes its size strNewVal = strHideWhiteSpace(txtReplace.Value) -- Retrieve new Replacement text and Confirmation toggle values -- V3.0 TblOption.Tgl.Conf = ( tglConfirm.Value == "ON" ) dlgPerform:destroy() return strButton, strNewVal end -- function SearchReplacePrompt -- Convert Media Path to Absolute and force ANSI encoding -- -- V2.9 function StrMediaToANSI(strPath,strFile) strFile = strFile:gsub("^[Mm]edia\\",function() return strPath.."Media\\" end) if string.encoding() == "ANSI" then return strFile end return fhConvertUTF8toANSI(strFile) end -- function StrMediaToANSI -- Perform Text Search and Optional Replace -- function DoSeekMatch(ptrItem,strDataClass) local strOldVal = fhGetValueAsText(ptrItem) local strNewVal = "" local strSeek = TblOption.Val.Seek -- V2.5 Val local strSwap = TblOption.Val.Swap local isOK = true -- V3.4 local function doManageString(strOldVal,strNewVal) -- V1.7 new function if TblOption.Set.Mode == "Replace" then if strDataClass == "richtext" then -- V3.2 -- Cater for richtext local arrLink = {} local ptrLink = fhNewItemPtr() ptrLink:MoveToFirstChildItem(ptrItem) -- Save any record link details -- V3.2 while ptrLink:IsNotNull() do local strLink = fhGetTag(ptrLink) if strLink:match("^_LINK_%u$") then local intLink = fhGetValueAsInteger(fhGetItemPtr(ptrLink,'~._LKID')) arrLink[intLink] = { Tag=strLink; Ptr=fhGetValueAsLink(ptrLink); } end ptrLink:MoveNext() end isOK = fhSetValueAsRichText(ptrItem,fhNewRichText(strNewVal))-- Update richtext field -- isOK -- V3.4 for intLink, dicLink in pairs(arrLink) do -- V3.3 ptrLink = fhCreateItem(dicLink.Tag,ptrItem) -- Reconstruct any record link details -- V3.2 if ptrLink:IsNotNull() then fhSetValueAsLink(ptrLink,dicLink.Ptr) ptrLink = fhCreateItem('_LKID',ptrLink) if ptrLink:IsNotNull() then fhSetValueAsInteger(ptrLink,intLink) end end end elseif strDataClass == "date" then -- V1.7 -- Cater for Date field local newDate = fhNewDate(1999) if strNewVal == "" then newDate:SetNull() -- Null Date Object for empty string else newDate:SetValueAsText(strNewVal,true) -- Allow Date Phrase for Date Object end isOK = fhSetValueAsDate(ptrItem,newDate) -- isOK -- V3.4 else if TblOption.Set.Data:match("FILE$") then -- Update linked Media file path? -- V2.9 local strPath = general.SplitFilename(fhGetContextInfo("CI_GEDCOM_FILE")) local strOldFile = StrMediaToANSI(strPath,strOldVal) local strNewFile = StrMediaToANSI(strPath,strNewVal) if general.FlgFileExists(strOldFile) then -- Ensure old Media file exists -- V2.9 local strNewPath = general.SplitFilename(strNewFile) if not general.FlgFolderExists(strNewPath) then -- Ensure new Media path exists -- V2.9 local strPath = "" for _, strPart in ipairs ( strNewPath:split("\\") ) do strPath = strPath..strPart.."\\" if strPath:match(":\\.+") then -- If not, create missing folders -- V2.9 general.MakeFolder(strPath,iup_gui.MemoDialogue) end end end if general.FlgFolderExists(strNewPath) then -- If new path exists rename file -- V2.9 local isOK, strErr = os.rename(strOldFile,strNewFile) local strOldPath = general.SplitFilename(strOldFile) local isOK, strErr = lfs.rmdir(strOldPath) -- Remove the old folder if empty -- V2.9 end end strNewVal = strNewVal:replace(strPath.."Media","Media") -- Alter absolute to relative path -- V2.9 end isOK = fhSetValueAsText(ptrItem,strNewVal) -- For text values use Text object -- isOK -- V3.4 end fhUpdateDisplay() end if not TblOption.Tgl.WhSp then -- V3.1 strOldVal = strShowWhiteSpace(strOldVal) strNewVal = strShowWhiteSpace(strNewVal) end local ptrRecord = fhNewItemPtr() -- Save result set entries ptrRecord:MoveToRecordItem(ptrItem) if not isOK then -- Replacement failed -- V3.4 if IsMemoFail then if 1 == iup_gui.WarnDialogue(" !! Replacement Failed ~ Old Value Retained !! "," The Result Set will list that error in the New Value column instead of the replacement: \n\n "..strNewVal.." \n\n Do you wish to inhibit these warning messages? \n\n","Yes","No") then IsMemoFail = false end end strNewVal = "!! Replacement Failed ~ Old Value Retained !!" end table.insert(TblRecord,ptrRecord:Clone()) table.insert(TblDataRef,TblOption.Set.Data) table.insert(TblOldValue,strOldVal) table.insert(TblNewValue,strNewVal) table.insert(TblRefValue,ptrItem:Clone()) -- V3.1 end -- local function doManageString if strDataClass == "richtext" then -- V3.1 -- Cater for richtext strOldVal = fhGetValueAsRichText(ptrItem):GetText() elseif strDataClass == "date" then -- V1.7 new conditional statement for Date field --# strOldVal = fhGetDisplayText(ptrItem):gsub("Date: ",""):gsub("Entry ","") strOldVal = fhGetItemText(ptrItem,fhGetTag(ptrItem)..":LONG") -- V2.5 end if TblOption.Tgl.Word then -- V2.1 revised to cater for successive repetitions of the seek word -- 1 Jan 2014 strNewVal = ("\02"..strOldVal.."\02"):gsub("(["..StrSP.."])","%1\02")-- Insert leading & trailing STX="\02" so first & last words match, and add STX="\02" to each white space/punctuation character so repeated words match strNewVal = strNewVal:gsub(strSeek,strSwap):gsub("\02","") -- Replace words and remove all inserted STX="\02" characters else strNewVal = strOldVal:gsub(strSeek,strSwap) -- Replace any other cases end if strOldVal ~= strNewVal then strNewVal = strNewVal:gsub("\01","") -- V2.0 Eliminate any SOH="\01" characters added by doPerformAction() to force a change local strWarning = nil if strDataClass == "age" then -- Obtain any Warning for Age field, with corrected text value strWarning,strNewVal = StrAgeWarning(ptrItem,strNewVal) elseif strDataClass == "date" then -- Obtain any Warning for Date field strWarning = StrDateWarning(ptrItem,strNewVal) end if TblOption.Tgl.Conf or strWarning then -- Confirmation required or Warning to report local strButton = nil strButton, strNewVal = SearchReplacePrompt(ptrItem,strOldVal,strNewVal,strWarning) if strButton == TblOption.Set.Mode then doManageString(strOldVal,strNewVal) -- V1.7 call new function elseif strButton ~= "Skip" then return false -- Process cancelled end else TblOption.Set.Data = general.BuildDataRef(ptrItem) doManageString(strOldVal,strNewVal) -- V1.7 call new function end end return true end -- function DoSeekMatch function strScopeType(strScope) -- Extract Search Scope record type tag called from DoAllItems, SetScopeMode, btnSomeRecs:action -- V2.8 return strScope:match("^(_?%u+)") end -- function strScopeType -- Search All Data Items -- function DoAllItems() local isContinue = true local intItem = tonumber(TblOption.Set.Item) -- Number of items in database local tblTgl = TblOption.Tgl -- Keyword array of Mode & Class & Tag boolean toggles local strScope = TblOption.Lst.Scope -- Search Scope filter -- V2.3 local arrScope = { } if #strScope == 0 then for strType in iterate.RecordTypes() do -- Search Scope record type tags are every type with _PLAC first -- V3.2 if strType == "_PLAC" then table.insert(arrScope,1,strType) -- This ensures Place record Place names get changed first else table.insert(arrScope,strType) end end else local strType = strScopeType(strScope) -- Search Scope record type tag -- V2.3 -- V2.4 -- V2.8 intItem = tonumber(TblOption.Set[strType]) or 0 -- Number of items in record type -- V2.3 table.insert(arrScope,strType) strScope = "^"..strScope:plain().."%." -- Search Scope filter tag(s) pattern -- V2.3 end local tblUser = iup_gui.DialogueAttributes("Main") tblUser.X = tblUser.CoordX+100 -- Popup Progress Bar in same place as Main dialogue tblUser.Y = tblUser.CoordY+150 progbar.Setup( tblUser ) if intItem > 5000 then progbar.Start("Searching",intItem) -- Start Progress Bar if enough items in database/record type end intItem = 0 -- V2.3 for _, strType in ipairs( arrScope ) do -- Loop through all record types in Scope -- V3.2 local strRecRoot = strType.."." -- Record. tag for item Search Scope -- V2.3 local tblRecs = TblOption.Set.Recs local useItem = true -- Is data item in Select Records filter? -- V2.8 progbar.Message("Searching "..strType.." Records") for ptrItem in iterate.Items(strType) do -- Loop through all data items if tblRecs then local intRid = fhGetRecordId(ptrItem) -- Select Records filter exists so check Record Id -- V2.8 if intRid > 0 then useItem = tblRecs[intRid] end -- Adjust flag only on record level data items -- V2.8 end if useItem then -- V2.8 local strChild = fhGetTag(ptrItem) -- Obtain child item Tag local ptrParent = fhNewItemPtr() ptrParent:MoveToParentItem(ptrItem) -- Obtain parent item pointer local strParent = fhGetTag(ptrParent) -- Obtain parent item Tag -- V2.3 if strParent == strType then strRecRoot = strType.."."..strChild.."." -- Record.Root. tags for Search Scope -- V2.3 end if strRecRoot:match(strScope) then -- Item matches Search Scope filter -- V2.3 if fhIsFact(ptrParent) then strParent = "~Fact." -- Parent is any Fact Tag elseif fhGetDisplayText(ptrParent):match("^LDS %l%l") then strParent = "~LDS." -- Parent is any LDS Ordinance else strParent = strParent.."." -- Otherwise use parent item Tag -- V2.3 ptrParent:MoveToParentItem(ptrParent) -- Prefix "~" to distinguish from Parent=Record if ptrParent:IsNotNull() then strParent = "~"..strParent end end local strParentChild = strParent..strChild -- Parent.Child Tags if fhIsAttribute(ptrItem) then strChild = "Attr" end -- Special case for all Attributes local strDataClass = GetDataClass(ptrItem) -- Fix for metafields -- V3.4 local strValueType = fhGetValueType(ptrItem) -- Only text & date fields are supported if ( strValueType:match("text$") or strValueType == "date" or strValueType == "age" ) -- V3.1 match("text$") for "text" & "richtext" and add "age" for FH V7 and ( tblTgl[strChild] -- Child item Tag or V2.0 "Attr" ? or tblTgl[strParentChild] -- V2.0 & V2.1 Parent.Child item Tags ? or ( tblTgl[strDataClass] and -- V2.0 Class match and tblTgl[strChild] == nil and -- Child and Parent.Child without any toggle option ? tblTgl[strParentChild] == nil ) ) then isContinue = DoSeekMatch(ptrItem,strDataClass) -- Try string value match for matching data item type end end if not isContinue then break end if progbar.Stop() then break end intItem = intItem + 1 if intItem > 99 then -- 1 Jan 2014 progbar.Step(100) -- Step progress bar every 100 items intItem = 0 collectgarbage("step",0) -- Memory garbage collection to avoid 'Not responding' -- V3.4 end end -- V2.8 end end progbar.Close() end -- function DoAllItems function CopyPreset(tblSource,tblTarget) -- Copy current options to/from chosen preset -- V2.5 if not tblSource then tblSource = {} end if not tblTarget then tblTarget = {} end for strHead, tblHead in pairs (tblSource) do if type(strHead) == "string" and strHead ~= "Set" then -- Only copy "Val", "Tgl", "Lst" dictionary tables if not tblTarget[strHead] then tblTarget[strHead] = {} end for strData, anyData in pairs (tblHead) do tblTarget[strHead][strData] = anyData end end end end -- function CopyPreset -- Manage Presets GUI -- -- V2.5 function ManagePresets() local intCurrent = TblOption.Set.Curr -- Save currently selected Preset from Main GUI local lstProject = iup.list { VisibleColumns=9; Multiple="Yes"; } -- Create GUI controls local lstProgram = iup.list { VisibleColumns=9; Multiple="Yes"; } -- V2.6 Multiple="Yes" local lstPhantom = iup.list { VisibleColumns=9; Multiple="Yes"; } -- V2.6 dummy for Export/Import local btnSaveAll = iup.button{ Title=">> Save >>"; } local btnLoadAll = iup.button{ Title="<< Load <<"; } local btnExport = iup.button{ Title="Export" ; } -- V2.6 and other button names shortened local btnImport = iup.button{ Title="Import" ; } -- V2.6 local btnMoveUp = iup.button{ Title="Move Up"; } local btnMoveDn = iup.button{ Title="Move Down"; } local btnUnlock = iup.button{ Title="Unlock" ; } local btnDelete = iup.button{ Title="Delete" ; } local lblPreset = iup.label { Title="New Preset Name :"; Alignment="ACENTER:ABOTTOM"; } -- V2.7 local txtPreset = iup.text { Value=""; } local btnInsert = iup.button{ Title="Insert" ; } local btnClone = iup.button{ Title="Clone" ; } local btnRename = iup.button{ Title="Rename" ; } local btnDestroy = iup.button{ Title="Finished"; action=function(self) return iup.CLOSE end; } local frmProject = iup.frame { Title=" Project List "; lstProject; } local frmProgram = iup.frame { Title=" Program List "; lstProgram; } local boxManager = iup.vbox { Homogeneous="Yes"; btnSaveAll; btnLoadAll; btnExport; btnImport; btnMoveUp; btnMoveDn; btnUnlock; btnDelete; lblPreset; txtPreset; btnInsert; btnClone; btnRename; btnDestroy; } -- V2.6 local frmManager = iup.frame { Title=" Manage Presets "; boxManager; } local boxPresets = iup.hbox { Homogeneous="Yes"; frmProject; frmManager; frmProgram; } local dlgPresets = iup.dialog{ Title=iup_gui.Plugin..iup_gui.Version; Gap=iup_gui.Gap; Margin="4x4"; boxPresets; } -- Set other GUI control attributes for iupName, tblAttr in pairs ( { -- Control = 1~FgColor ; 2~Font ; 3~Tip ; [frmProject] = { iup_gui.Head; iup_gui.FontHead; }; [lstProject] = { iup_gui.Body; iup_gui.FontBody; "Select any local Project Presets in list"; }; [frmProgram] = { iup_gui.Head; iup_gui.FontHead; }; [lstProgram] = { iup_gui.Body; iup_gui.FontBody; "Select any global Program Presets in list"; }; [frmManager] = { iup_gui.Head; iup_gui.FontHead; }; [btnSaveAll] = { iup_gui.Warn; iup_gui.FontBody; "Copy selected Project Presets to Program List"; }; [btnLoadAll] = { iup_gui.Warn; iup_gui.FontBody; "Copy selected Program Presets to Project List"; }; [btnExport] = { iup_gui.Warn; iup_gui.FontBody; "Export any selected Presets to a File" ; }; -- V2.6 [btnImport] = { iup_gui.Warn; iup_gui.FontBody; "Import from a File to the Project List"; }; -- V2.6 [btnMoveUp] = { iup_gui.Safe; iup_gui.FontBody; "Move the selected Preset up the list" ; }; [btnMoveDn] = { iup_gui.Safe; iup_gui.FontBody; "Move the selected Preset down the list"; }; [btnUnlock] = { iup_gui.Risk; iup_gui.FontBody; "Unlock the selected Preset to allow deletion"; }; [btnDelete] = { iup_gui.Risk; iup_gui.FontBody; "Delete the selected Preset from the list"; }; [lblPreset] = { iup_gui.Body; iup_gui.FontBody; "Enter new Preset name for Insert, Clone, or Rename"; }; [txtPreset] = { iup_gui.Safe; iup_gui.FontBody; "Enter new Preset name for Insert, Clone, or Rename"; }; [btnInsert] = { iup_gui.Safe; iup_gui.FontBody; "Insert a blank Preset using New Preset Name"; }; [btnClone ] = { iup_gui.Safe; iup_gui.FontBody; "Clone selected Preset using New Preset Name"; }; [btnRename] = { iup_gui.Safe; iup_gui.FontBody; "Rename selected Preset using New Preset Name"; }; [btnDestroy] = { iup_gui.Risk; iup_gui.FontBody; "Finished Managing Presets"; }; } ) do iupName.Expand = "Yes" iupName.FgColor = tblAttr[1] iupName.Font = tblAttr[2] iupName.Tip = tblAttr[3] iupName.TipBalloon = iup_gui.Balloon end local function intPresetChosen(lstPreset) -- Return number of selected items in Preset List local _,intChosen = lstPreset.Value:replace("+","+") return intChosen end -- local function intPresetChosen local function intPresetFirst(lstPreset) -- Return position of first selected item in Preset List return lstPreset.Value:find("+",1,true) or 0 end -- local function intPresetFirst local function strPresetValue(lstPreset,intPreset) -- Return list value for a Preset List local strValue = "" local intCount = tonumber(lstPreset.Count) or 0 for intValue = 1, math.max(intCount,intPreset or 0) do if intValue == intPreset then -- Compose list control value string strValue = strValue.."+" else strValue = strValue.."-" end end if lstPreset == lstProject and intPreset then intCurrent = intPreset -- Update current selected Project Preset end return strValue end -- local function strPresetValue local function isLocked(intValue,tblOption) -- Is current Preset locked? if intValue > 0 then return ( tblOption.Set.List[intValue].Lock == "ON" ) end return false end -- local function isLocked local function newPreset(intValue,tblOption,strValue) -- Is new Preset value unique in chosen list if intValue > 0 then for intList, dicList in ipairs (tblOption.Set.List or {}) do if dicList.Name == strValue then return false end -- No it already exists in list end end return true end -- local function newPreset local function doDisableButtons() -- Set button controls inactive for intName = 1, 20 do local iupName = boxManager[intName] if iupName == btnDestroy then break end -- Make each button inactive except btnDestroy if iup.ClassMatch(iupName,"button") then iupName.Active = "No" end end end -- local function doDisableButtons local function doEnableButtons() -- Set button controls active status local intProjMax = tonumber(lstProject.Count) or 0 local intProgMax = tonumber(lstProgram.Count) or 0 -- Total count of presets in both lists local intProject = intPresetChosen(lstProject) local intProgram = intPresetChosen(lstProgram) -- Total selected presets in both lists local intProj1st = intPresetFirst(lstProject) local intProg1st = intPresetFirst(lstProgram) -- First selected preset in both lists doDisableButtons() if intProject > 0 then btnSaveAll.Active = "Yes" end -- If any selected preset then activate Save/Load buttons if intProgram > 0 then btnLoadAll.Active = "Yes" end if (intProject+intProgram) > 0 then btnExport.Active = "Yes" end -- Activate Export and Import buttons -- V2.6 btnImport.Active = "Yes" if (intProject+intProgram) == 1 then -- If one selected preset in either list if isLocked(intProj1st,TblOption) or isLocked(intProg1st,ArrOption) then btnUnlock.Active = "Yes" -- Activate Unlock button else btnDelete.Active = "Yes" -- Activate Delete button end if #txtPreset.Value > 0 and newPreset(intProj1st,TblOption,txtPreset.Value) and newPreset(intProg1st,ArrOption,txtPreset.Value) then -- If new Preset name is valid btnInsert.Active = "Yes" btnClone .Active = "Yes" -- Activate Insert/Clone/Rename buttons btnRename.Active = "Yes" end if intProg1st > 1 -- If not first preset selected then activate Move Up button or intProj1st > 1 then btnMoveUp.Active = "Yes" end if ( intProg1st > 0 and intProg1st < intProgMax ) -- If not last preset selected then activate Move Down button or ( intProj1st > 0 and intProj1st < intProjMax ) then btnMoveDn.Active = "Yes" end end end -- local function doEnableButtons function lstProject:action(strText,intItem,intState) -- Select presets from Project List lstProgram.Value = strPresetValue(lstProgram) -- Deselect presets in Program List intCurrent = intPresetFirst(lstProject) -- Update current selected Project Preset doEnableButtons() end -- function lstProject:action function lstProgram:action(strText,intItem,intState) -- Select presets from Program List lstProject.Value = strPresetValue(lstProject) -- Deselect presets in Project List doEnableButtons() end -- function lstProgram:action local function doCopyOne(lstSource,tblSource,lstTarget,tblTarget) -- Copy one Preset from source to target list local strPreset = nil -- V2.7 local intSource = intPresetFirst(lstSource) if intSource > 0 then local arrSource = tblSource.Set.List local arrTarget = tblTarget.Set.List local intTarget = 0 local strSource = arrSource[intSource].Name for intList, dicList in ipairs (arrTarget) do -- Find source preset name in target list if strSource == dicList.Name then intTarget = intList break end end if intTarget == 0 then -- No target name found so insert new one lstTarget.AppendItem = strSource table.insert(arrTarget,{}) intTarget = #arrTarget end lstSource.Value = strPresetValue(lstSource) lstTarget.Value = strPresetValue(lstTarget,intTarget) -- Select target preset as a visual cue local intAns = 1 if arrTarget[intTarget].Lock == "ON" then intAns = iup_gui.WarnDialogue("Preset Locked","\n Preset named '"..strSource.."' is Locked. \n\n Continue to overwrite this Preset? \n","Yes","No") end if intAns == 1 then arrTarget[intTarget].Name = strSource arrTarget[intTarget].Lock = arrSource[intSource].Lock tblTarget[intTarget] = {} CopyPreset(tblSource[intSource],tblTarget[intTarget]) -- Copy the preset options strPreset = strSource -- V2.7 end end return strPreset -- V2.7 end -- local function doCopyOne local function doCopyAll(lstSource,tblSource,lstTarget,tblTarget) local arrPreset = {} local strSource = lstSource.Value -- Remember original selected presets local strTarget = lstTarget.Value for intSource = 1, #tblSource do -- Loop through source list if strSource:sub(intSource,intSource) == "+" then lstSource.Value = strPresetValue(lstSource,intSource) -- Copy the preset options slowly fhSleep(100,50) table.insert(arrPreset,doCopyOne(lstSource,tblSource,lstTarget,tblTarget)) -- V2.7 arrPreset fhSleep(100,50) end end lstSource.Value = strSource -- Restore original selected presets lstTarget.Value = strTarget return arrPreset end -- local function doCopyAll function btnSaveAll:action() -- Save All presets button action doDisableButtons() doCopyAll(lstProject,TblOption,lstProgram,ArrOption) doEnableButtons() end -- function btnSaveAll:action function btnLoadAll:action() -- Load All presets button action doDisableButtons() doCopyAll(lstProgram,ArrOption,lstProject,TblOption) doEnableButtons() end -- function btnLoadAll:action local function doExport(lstSource,tblSource) -- Export selected Presets to File if intPresetChosen(lstSource) > 0 then local filedlg = iup.filedlg{ dialogtype="SAVE"; Title="Choose Presets Export File"; directory=iup_gui.PublicPath; file="Presets.fhdata"; extfilter="Preset files (*.fhdata)|*.fhdata|"; } filedlg:popup(iup.CENTER,iup.CENTER) if filedlg.status ~= "-1" then -- File chosen OK local strFileName = filedlg.value local strFolder, strExport = general.SplitFilename(strFileName) if general.FlgFolderWrite(strFolder) then -- If chosen folder is writeable then all OK fhSleep(200,100) local tblExport = { Set={ List={}; }; } doCopyAll(lstSource,tblSource,lstPhantom,tblExport) -- Save selected Presets to Export table in File table.save(tblExport,strFileName) lstPhantom.RemoveItem = "ALL" fhSleep(200,100) else iup_gui.WarnDialogue("Export Failed","\n Export file '"..strExport.."' cannot be saved to folder. \n") end end end end -- local function doExport function btnExport:action() -- Export presets button action -- V2.6 doDisableButtons() doExport(lstProject,TblOption) doExport(lstProgram,ArrOption) doEnableButtons() end -- function btnExport:action local function doImport(strFileName,strImport) -- Load Import table from File -- V2.7 local tblImport, strError = table.load(strFileName) if strError then iup_gui.WarnDialogue("Import Error","\n Import File '"..strImport.."' Load Error: \n"..strError.."\n") return nil end for intImport, arrImport in ipairs (tblImport) do if not (arrImport.Tgl and arrImport.Lst and arrImport.Val) or not (tblImport.Set and tblImport.Set.List) or not (tblImport.Set.List[intImport] and tblImport.Set.List[intImport].Name and tblImport.Set.List[intImport].Lock) then iup_gui.WarnDialogue("Import Error","\n Import File '"..strImport.."' Format Error. \n") return nil end end return tblImport end -- local function doImport function btnImport:action() -- Import presets button action -- V2.6 doDisableButtons() local filedlg = iup.filedlg{ dialogtype="OPEN"; Title="Choose Presets Import File"; directory=iup_gui.PublicPath; extfilter="Preset files (*.fhdata)|*.fhdata|All files (*.*)|*.*|"; } filedlg:popup(iup.CENTER,iup.CENTER) if filedlg.status ~= "-1" then -- File chosen OK local strFileName = filedlg.value if general.FlgFileExists(strFileName) then local strFolder, strImport = general.SplitFilename(strFileName) local tblImport = doImport(strFileName,strImport) -- Load & check Import table from File -- V2.7 if tblImport then fhSleep(200,100) lstPhantom.Value = string.rep("+",#tblImport) -- Copy Import table to Project List -- V2.7 local arrPreset = doCopyAll(lstPhantom,tblImport,lstProject,TblOption) fhSleep(200,100) iup_gui.MemoDialogue("\n File '"..strImport.."' imported "..#arrPreset.." Presets. \n\n"..table.concat(arrPreset,"\n").."\n") -- V2.7 end end end doEnableButtons() end -- function btnImport:action local function doMove(lstName,intDir,tblOption) -- Move preset up/down one position local intValue = intPresetFirst(lstName) if intValue > 0 then local tblPreset = tblOption.Set.List local intNext1 = intValue + intDir -- New position is one above/below chosen preset local strTemp = lstName[intValue] lstName[intValue] = lstName[intNext1] -- Swap GUI list entries lstName[intNext1] = strTemp local tblTemp = tblPreset[intValue] tblPreset[intValue] = tblPreset[intNext1] -- Swap associated table items tblPreset[intNext1] = tblTemp lstName.Value = strPresetValue(lstName,intNext1) -- Select the moved preset tblTemp = {} CopyPreset(tblOption[intValue],tblTemp) CopyPreset(tblOption[intNext1],tblOption[intValue]) -- Swap the preset options CopyPreset(tblTemp,tblOption[intNext1]) end end -- local function doMove function btnMoveUp:action() -- Move preset Up button action doMove(lstProject,-1,TblOption) doMove(lstProgram,-1,ArrOption) doEnableButtons() end -- function btnMoveUp:action function btnMoveDn:action() -- Move preset Down button action doMove(lstProject,1,TblOption) doMove(lstProgram,1,ArrOption) doEnableButtons() end -- function btnMoveDn:action local function doUnlock(lstName,tblOption) -- Unlock any preset from list local intValue = intPresetFirst(lstName) if intValue > 0 then tblOption.Set.List[intValue].Lock = "OFF" end end -- local function doUnlock function btnUnlock:action() -- Unlock preset button action doUnlock(lstProject,TblOption) doUnlock(lstProgram,ArrOption) doEnableButtons() end -- function btnUnlock:action local function doDelete(lstName,tblOption) -- Delete any preset from list local intValue = intPresetFirst(lstName) if intValue > 0 then local arrPreset = tblOption.Set.List lstName.RemoveItem = intValue -- Remove selected Preset from GUI list & associated table table.remove(arrPreset,intValue) local intCount = #arrPreset for intItem = intValue, intCount do -- Move the preset options up to close gap CopyPreset(tblOption[intItem+1],tblOption[intItem]) end tblOption[intCount+1] = nil tblOption[intCount+2] = nil intValue = math.max(1,intValue-1) -- Select 1st preset or preset above removed preset lstName.Value = strPresetValue(lstName,intValue) end end -- local function doDelete function btnDelete:action() -- Delete preset button action doDelete(lstProject,TblOption) doDelete(lstProgram,ArrOption) doEnableButtons() end -- function btnDelete:action function txtPreset:valuechanged_cb() -- Set buttons after preset name changed doEnableButtons() end -- function txtPreset:valuechanged_cb local function doInsert(lstName,tblOption,btnAction) -- Insert/Clone new preset into list local intValue = intPresetFirst(lstName) if intValue > 0 then local arrPreset = tblOption.Set.List local strPreset = txtPreset.Value intValue = intValue + 1 lstName["InsertItem"..intValue] = strPreset -- Insert new Preset name into GUI list & associated table table.insert(arrPreset,intValue,{Name=strPreset;Lock="OFF";}) local intCount = #arrPreset tblOption[intCount] = {} for intItem = intCount, intValue, -1 do -- Move the preset options down CopyPreset(tblOption[intItem-1],tblOption[intItem]) -- Leave chosen options in clone end if btnAction == btnInsert then UserDefaultOptions(tblOption[intValue]) -- Reset the options for insert end txtPreset.Value = "" lstName.Value = strPresetValue(lstName,intValue) -- Select inserted/cloned preset end end -- local function doInsert function btnInsert:action() -- Insert preset button action doInsert(lstProject,TblOption,btnInsert) doInsert(lstProgram,ArrOption,btnInsert) doEnableButtons() end -- function btnInsert:action function btnClone:action() -- Clone preset button action doInsert(lstProject,TblOption,btnClone) doInsert(lstProgram,ArrOption,btnClone) doEnableButtons() end -- function btnRename:action local function doRename(lstName,tblOption) -- Rename any preset in list local intValue = intPresetFirst(lstName) if intValue > 0 then local arrPreset = tblOption.Set.List local strPreset = txtPreset.Value lstName [intValue] = strPreset -- Put new Preset name in GUI list & associated table arrPreset[intValue].Name = strPreset txtPreset.Value = "" lstName.Value = strPresetValue(lstName,intValue) -- Select renamed preset end end -- local function doRename function btnRename:action() -- Rename preset button action doRename(lstProject,TblOption) doRename(lstProgram,ArrOption) doEnableButtons() end -- function btnRename:action for intList, dicList in ipairs (TblOption.Set.List or {}) do -- Load the Project Preset list names lstProject[intList] = dicList.Name end for intList, dicList in ipairs (ArrOption.Set.List or {}) do -- Load the Program Preset list names lstProgram[intList] = dicList.Name end lstProject.Value = strPresetValue(lstProject,intCurrent) -- Select same Preset as in main GUI lstProgram.Value = strPresetValue(lstProgram) doEnableButtons() iup_gui.ShowDialogue("Preset",dlgPresets,btnDestroy,"Normal Keep") -- Popup dialogue that remembers its attributes & refreshes its size TblOption.Set.Curr = intCurrent -- Update currently selected Preset in Main GUI end -- function ManagePresets -- Main GUI Dialogue -- function GUI_MainDialogue() --[[ TblOption.Set has these entries for global option settings : Item Item count of database size for search progress bar Date Date of last Item update to avoid recalculation run-time List Droplist of the Project Preset names Curr Current chosen Project Preset Mode "Replace" = Search & Replace or "Report" = Search ONLY (not sticky) Data Data reference for currently matched item (not sticky) TblOption.Val has these values for search & replace strings : Seek Search text string Swap Replace text string TblOption.Tgl and TblOption.Lst and other tables below may have these entries for "ON"/"OFF" toggles or combo drop list Tags : Mixed case entries are Mode settings : Text Plain Text Mode v. LUA Pattern Text Mode Case Case Insensitive v. Case Sensitive Word Whole Words v. Part Words WhSp Hide v. Show white space symbols -- V3.0 Recs All Records v. Chosen Records -- V2.8 Scope Set Search Scope combo drop list -- V2.3 Conf Confirm Text Replacements Attr Attribute Values enabled/disabled -- V2.0 Age Age Error checks enabled/disabled -- V2.1 Date Date Warnings enabled/disabled -- V1.7 Phrase Date Phrase Warnings enabled/disabled -- V2.2 Lower case entries are single Data Class names such as "text", "name", "place" and "longtext" Upper case entries are single Gedcom Tag names such as "NICK", "FORM", "_PLAC" and "_EMAIL" Upper case entries separated by a dot are Record parent & child Tag names such as "SOUR.TITL" Upper case entries prefixed by a tilda "~" are non-Record parent & child such as "~DATA.TEXT" These latter are used where similar Gedcom Tag names need to be differentiated : INDI.NAME "name" (Individual Name) versus REPO.NAME & SUBM.NAME "text" (Repository/Submitter record Name c.f. Source/Media record Title) INDI.TITL "text" (Title Attribute) versus SOUR.TITL "longtext" & SOUR.ABBR "text" & OBJE.TITL "text" (Source/Media record Title) NOTE.TEXT (Note record Text) versus SOUR.TEXT & ~DATA.TEXT (Text From Source) all "longtext" or "richtext" in FH V7 Special variants are ~Fact.NOTE & ~Fact.DATE for any Fact parent tags, and ~LDS.NOTE & ~LDS.DATE for any LDS Ordinance parent tags. --]] local tblSet = TblOption.Set -- Local shortcuts to global option tables local tblVal = TblOption.Val local tblTgl = TblOption.Tgl local tblLst = TblOption.Lst local tblBasic = { } -- GUI basic filter toggle iup controls local tblExtra = { } -- GUI extra filter toggle iup controls local tblCombo = { } -- GUI combo filter drop list iup controls local tblRelated = { -- Related GUI settings dependancies -- Short Text fields: "text", "word", "name", "place", etc... text = { "word"; "word2"; "latlong"; "enumword"; "wordlist"; };-- FORM = "word"; ROLE & TYPE = "word2"; LATLONG = "latlong"; QUAY, PEDI, etc = "enumword"; "wordlist"?; remainder are "text", except NAME/"name" & PLAC/"place" below -- V3.1 name = { "NPFX"; "NSFX"; "NICK"; "GIVN"; "_USED"; "SURN"; "SPFX"; "~NAME.FONE"; "~NAME.ROMN"; }; -- V3.1 add FONE & ROMN PLAC = { "place"; "_PLAC"; "~PLAC.FONE"; "~PLAC.ROMN"; }; -- V3.1 add FONE & ROMN RFN = { "RIN" ; }; FORM = { }; -- V2.0 FORM is "word" for OBJE.FORM & OBJE.FILE.FORM and PLAC.FORM FILE = { "_FILE"; }; -- V2.9 add FILE ["~_FIELD.TEXT"]= { "~_FIELD.NAME"; "~_FIELD.DATE"; "~_FIELD.PLAC"; "~_FIELD.ENUM"; "~_FIELD.URL"; }; -- V3.1 -- "name", "date", "place", "text", "url" PHON = { "EMAIL"; "WWW" ; "_WEB"; "_EMAIL"; }; -- V3.1 add EMAIL & WWW ADR1 = { "ADR2" ; "ADR3"; "CITY"; "STAE"; "POST"; "CTRY"; }; -- V3.1 add ADR3 TEMP = { "AFN" ; "FAMF"; }; -- INDI.CONL.TEMP & SUBN.TEMP & INDI.AFN & SUBN.FAMF _SRCT = { "_SRCT.NAME"; "_SRCT.COLL"; "_SRCT.CATG"; "_SRCT.SUBC"; "_SRCT.TITL"; "_SRCT.BIBL"; "_SRCT.FOOT"; "_SRCT.SHRT"; "~FDEF.NAME"; "~FDEF.PROM"; }; -- V3.1 -- Long Text fields: "longtext" or "richtext" in FH V7 NOTE2 = { "_NOTE"; "_NOTA"; "_SRCT.DESC"; "~FDEF.DESC" }; -- V3.1 add _NOTA & _SRCT.DESC & ~FDEF.DESC ["SOUR.TEXT"] = { "~DATA.TEXT"; }; ["NOTE.TEXT"] = { "_RNOT.TEXT"; }; -- V3.1 add _RNOT.TEXT -- Distinctive fields: "text" & "longtext" & "richtext" mix & "date" ["SOUR.TITL"] = { "SOUR.ABBR"; "OBJE.TITL"; "~FILE.TITL"; "REPO.NAME"; "_PLAC.TEXT"; "_SRCT.NAME"; "SUBM.NAME"; }; -- V3.1 add ~FILE.TITL & _PLAC.TEXT & _SRCT.NAME } local function setFilterItems(arrFilter) -- Create dictionaries for array indexes -- V3.1 arrFilter.Item = {} if arrFilter.Name == "Scope" then for intItem, strItem in ipairs (arrFilter) do -- SearchScope special case items strItem = strItem:gsub(" %(.-%)$","") or "?" arrFilter.Item[strItem] = intItem end else for intName, arrName in ipairs (arrFilter) do -- Other filters Name items local strName = arrName.Name or "?" arrFilter.Item[strName] = intName arrName.Item = {} local noItems = true for intItem, strItem in ipairs (arrName) do -- Name list items strItem = strItem:gsub(" %(.-%)$","") arrName.Item[strItem] = intItem noItems = false end if noItems then arrName.Item = nil end end end end -- local function setFilterItems local tblFilters = { } -- GUI basic, extra & combo filter attributes -- V2.1 -- Name of filter= Title & Tip for toggle used in two filter tables; tblFilters.name = { Title="Individual Names, Prefix, Suffix, Given, etc"; Tip_T="All Individual Primary and Alternate Name (NAME) fields including: \n (NPFX) Name Prefix, \n (NSFX) Name Suffix, \n (NICK) Nickname, \n (GIVN) Given Name, \n (_USED) Given Name Used, \n (SURN) Surname, \n (SPFX) Surname Prefix"; } tblFilters.PLAC = { Title="Place fields"; -- V2.5 Tip_T="Fact Place (PLAC) and To/From (_PLAC) and Source Event Place (PLAC) fields"; } tblFilters.PAGE = { Title="Where Within Source fields"; Tip_T="Where Within Source (PAGE) fields for Citations"; } tblFilters.NOTE2 = { Title="Note fields"; Tip_T="All the Note and Link/Note (NOTE/_NOTE) fields"; } tblFilters.ADDR = { Title="Address fields"; -- V2.5 Tip_T="Fact, Repository & Submitter Address (ADDR) fields"; } tblFilters.TEXT = { Title="Text From Source fields"; Tip_T="Text From Source (TEXT) fields for Citations and Sources"; } tblFilters.date = { Title="Date fields"; -- V2.5 moved Tip_T="Fact, Citation Entry, Multimedia, and LDS Date (DATE/_DATE) fields"; } -- Fact DATE, Citation DATA.DATE, Multimedia OBJE._DATE, LDS Ordination BAPL/CONL/ENDL/SLGC/SLGS.DATE fields if fhGetAppVersion() > 6 then -- FH V7 tip variants -- V3.1 tblFilters.name .Tip_T = tblFilters.name.Tip_T..", \n (FONE) Phonetic Name, \n (ROMN) Romanized Name" tblFilters.PLAC .Tip_T = tblFilters.PLAC.Tip_T..", \n and Phonetic Place (FONE) and Romanized Place (ROMN) fields" tblFilters.NOTE2.Tip_T = "All local Note (NOTE) and Description (DESC) fields" tblFilters.NOTE2.Title = "Note && Description fields" tblFilters.date .Tip_T = tblFilters.date.Tip_T:replace("Entry","Entry, Source Metafield") -- Source Metafield ~_FIELD.DATE end tblFilters.SearchScope = { -- Search Scope special combo drop list -- V2.3 -- Name of filter; Tip & Items for drop list; Name="Scope"; Tip_L="Set global search scope for Basic/Extra Filters"; "All Records & Events/Attributes"; "Individual Records (INDI)"; "Family Records (FAM)"; "Note Records (NOTE)"; "Source Records (SOUR)"; "Repository Records (REPO)"; "Multimedia Records (OBJE)"; "Submitter Records (SUBM)"; "Submission Records (SUBN)"; fhFunc=fhIsFact; tblTag={}; Cols=20; -- Append all Events & Attributes, nothing excluded, with more VisibleColumns } setFilterItems(tblFilters.SearchScope) -- V3.1 local tblScope = tblFilters.SearchScope local intScope = tblScope.Item["Submitter Records"] if fhGetAppVersion() > 5 then -- FH V6 record scope -- V2.4/5 table.insert(tblScope,intScope,"Place Records (_PLAC)") -- Insert Place Records item before Submitter Records item end if fhGetAppVersion() > 6 then -- FH V7 record scope -- V3.1 table.insert(tblScope,intScope+1,"Research Note Records (_RNOT)") -- Insert these two after Place Records item table.insert(tblScope,intScope+2,"Source Template Records (_SRCT)") end tblFilters.BasicShort = { -- Basic Filter Short Text toggles -- V2.1 -- Name of filter; Title & Tip for toggle; { Name="name"; Title=tblFilters.name.Title; Tip_T=tblFilters.name.Tip_T; }; { Name="PLAC"; Title=tblFilters.PLAC.Title; -- V2.5 Tip_T=tblFilters.PLAC.Tip_T; }; { Name="PAGE"; Title=tblFilters.PAGE.Title; Tip_T=tblFilters.PAGE.Tip_T; }; { Name="text"; Title="All other Text fields"; Tip_T="All other Short Text and Long Text fields"; }; } tblFilters.BasicLong = { -- Basic Filter Long Text toggles -- V2.1 -- Name of filter; Title & Tip for toggle; { Name="NOTE2"; Title=tblFilters.NOTE2.Title; Tip_T=tblFilters.NOTE2.Tip_T; }; { Name="ADDR"; Title=tblFilters.ADDR.Title; -- V2.5 Tip_T=tblFilters.ADDR.Tip_T; }; { Name="SOUR.TEXT"; Title=tblFilters.TEXT.Title; Tip_T=tblFilters.TEXT.Tip_T; }; { Name="date"; Title=tblFilters.date.Title; -- V2.5 moved Tip_T=tblFilters.date.Tip_T; }; } tblFilters.ShortText = { -- Extra Filter Short Text toggles & combo drop lists -- Name of filter; Title & Tip for toggle and Tip & Items for drop list; { Name="name"; Title=tblFilters.name.Title; Tip_T=tblFilters.name.Tip_T; }; { Name="PLAC"; Title=tblFilters.PLAC.Title; -- V2.5 Tip_T=tblFilters.PLAC.Tip_T; }; { Name="PAGE"; Title=tblFilters.PAGE.Title; Tip_T=tblFilters.PAGE.Tip_T; }; { Name="Attr"; Title="Fact Attribute values"; Tip_T="Any value for Fact Attributes"; Tip_L="Select an Attribute fact"; "All the Attribute facts"; fhFunc=fhIsAttribute; tblTag={RESI=true;}; }; -- Append all Attributes with values, so exclude Residence (RESI) { Name="CAUS"; Title="Fact Cause fields"; Tip_T="Fact Cause (CAUS) fields"; }; { Name="AGNC"; Title="Fact && Source Responsible Agency fields"; Tip_T="Fact and Source Responsible Agency (AGNC) fields"; }; { Name="_TYPE"; Title="Type fields for Source records"; Tip_T="Type (_TYPE) fields for Source records"; }; { Name="TYPE"; Title="Type fields for Facts && Custom Id"; Tip_T="Type (TYPE) fields for Facts and Custom Id"; }; { Name="REFN"; Title="Custom Id fields"; Tip_T="Custom Id (REFN) fields"; }; { Name="RFN"; Title="Permanent && Automated Record Number fields"; Tip_T="Permanent (RFN) and Automated (RIN) Record Number fields"; }; { Name="FORM"; Title="Multimedia Format && Place Hierarchy fields"; -- V2.9 Tip_T="Multimedia Format and Place Hierarchy (FORM) fields"; }; { Name="FILE"; Title="Multimedia Linked File fields && Media files"; -- V2.9 Tip_T="Multimedia Linked File (FILE/_FILE) fields \n and associated Media path names"; }; { Name="_EXCL"; Title="Multimedia Exclude flags"; -- V3.1 Tip_T="Multimedia Exclude from Diagrams & Reports (_EXCL) flags"; }; { Name="_KEYS"; Title="Multimedia Keyword fields"; Tip_T="Multimedia Keyword (_KEYS) fields"; }; { Name="PHON"; Title="Phone && Email && Website fields"; Tip_T="All the Phone (PHON) and Email (_EMAIL) and Website (_WEB) fields"; }; { Name="ADR1"; Title="Address Line1/2, City, State, Postcode && Country"; Tip_T="All address Line1 (ADR1), Line2 (ADR2), City (CITY), \n State (STAE), Postcode (POST) and Country (CTRY) fields"; }; { Name="TEMP"; Title="LDS Temple && File Number && Filename fields"; Tip_T="LDS Temple Codes (TEMP), Ancestral File Number (AFN)\n and Family Filename (FAMF) fields"; }; { Name="text"; Title="All other one line Short Text fields"; Tip_T="All other one line Short Text fields including: \n (\"UDF\") Uncategorised Data Fields, \n (_SENT) Fact Customised Sentences, \n (QUAY) Citation Assessments, \n (ROLE) Citation Event Type Roles, \n (RELA) Association Relationships"; }; --[[ Uncategorised Data Fields (UDF), Fact Customised Sentences (Fact._SENT,Fact._SHAR._SENT,Fact._SHAN._SENT), Fact Witness Roles Titles (Fact._SHAR.ROLE,Fact._SHAN.ROLE), Citation Assessment Types (Fact.SOUR.QUAY/_QUAY enumword), Citation Event Type Roles (Fact.SOUR.EVEN.ROLE word2), Association Relationships (INDI.ASSO.RELA), Family Child Relationship (INDI.FAMC.PEDI/_PEDI enumword), Place Lat/Longitude Field (_PLAC.MAP.LATLONG, latlong), Submitter Record Language (SUBM.LANG), Source Repository Identifications (SOUR.REPO.CALN), etc... --]] } setFilterItems(tblFilters.ShortText) -- V3.1 local tblShort = tblFilters.ShortText local intText = tblShort.Item["text"] if fhGetAppVersion() > 5 then -- FH V6 ShortText variants -- V2.4 tblShort[intText].Tip_T = tblShort[intText].Tip_T:replace("Event Type","Event Type & Witness"):replace("Relationships","Relationships, \n (LATLONG) Place Lat/Longitude Fields") end if fhGetAppVersion() > 6 then -- FH V7 ShortText variants -- V3.1 local intType = tblShort.Item["TYPE"] tblShort[intType].Title = tblShort[intType].Title.." && Phonetic/Roman Names/Places" tblShort[intType].Tip_T = tblShort[intType].Tip_T.."\n and Phonetic or Romanized Names or Places" local intFile = tblShort.Item["FILE"] tblShort[intFile].Tip_T = tblShort[intFile].Tip_T:replace("/_FILE","") local intPhon = tblShort.Item["PHON"] tblShort[intPhon].Tip_T = tblShort[intPhon].Tip_T:replace("_EMAIL","EMAIL"):replace("_WEB","WWW") local intAddr = tblShort.Item["ADR1"] tblShort[intAddr].Title = tblShort[intAddr].Title:replace("Line1/2","Line1/2/3") tblShort[intAddr].Tip_T = tblShort[intAddr].Tip_T:replace("(ADR2)","(ADR2), Line3 (ADR3)") table.insert(tblShort,intText, -- Insert items from bottom up to avoid upsetting indexes { Name="_SRCT"; Title="Source Template record fields "; Tip_T="Source Template record (_SRCT) short text fields"; Tip_L="Select a Source Template record short text field"; "All Template short text fields"; "Name (_SRCT.NAME)"; "Collection (_SRCT.COLL)"; "Category (_SRCT.CATG)"; "Subcategory (_SRCT.SUBC)"; "Bibliography (_SRCT.BIBL)"; "Footnote (_SRCT.FOOT)"; "Short Footnote (_SRCT.SHRT)"; "Metafield Name (~FDEF.NAME)"; "Metafield Prompt (~FDEF.PROM)"; } ) table.insert(tblShort,intType, { Name="~_FIELD.TEXT"; Title="Templated Source metafields"; Tip_T="Templated Source record (_FIELD) metafields"; Tip_L="Select a Templated Source metafield"; "All Templated Source metafields"; "Text (~_FIELD.TEXT)"; "Name (~_FIELD.NAME)"; "Date (~_FIELD.DATE)"; "Enum (~_FIELD.ENUM)"; "URL (~_FIELD.URL)"; } ) end tblFilters.LongText = { -- Extra Filter Long Text toggles & combo drop lists -- Name of filter; Title & Tip for toggle and Tip & Items for drop list; { Name="NOTE2"; Title=tblFilters.NOTE2.Title; Tip_T=tblFilters.NOTE2.Tip_T; Tip_L="Select a Note field type"; "All the Note fields"; "Fact Notes (~Fact.NOTE)"; "Individual (INDI.NOTE)"; "Family (FAM.NOTE)"; "Citation (~SOUR.NOTE)"; "Source (SOUR.NOTE)"; "Repository (REPO.NOTE)"; "Multimedia (OBJE._NOTE)"; "Link/Notes (OBJE.NOTE)"; "LDS Ordination (~LDS.NOTE)"; "Last Change (~CHAN.NOTE)"; }; { Name="ADDR"; Title=tblFilters.ADDR.Title; -- V2.5 Tip_T=tblFilters.ADDR.Tip_T; }; { Name="SOUR.TEXT"; Title=tblFilters.TEXT.Title; Tip_T=tblFilters.TEXT.Tip_T; Tip_L="Select Citations or Sources"; "Both the field types"; "Citation (~DATA.TEXT)"; "Source (SOUR.TEXT)"; }; { Name="AUTH"; Title="Author fields for Sources"; Tip_T="Author (AUTH) fields for Sources"; }; { Name="PUBL"; Title="Publication Info fields for Sources"; Tip_T="Publication Info (PUBL) fields for Sources"; }; { Name="SOUR2"; Title="Source Note fields"; Tip_T="Source Note (SOUR) fields"; }; { Name="NOTE.TEXT"; Title="Note record Text fields"; Tip_T="Note record Text (TEXT) fields"; }; } setFilterItems(tblFilters.LongText) -- V3.1 local tblLong = tblFilters.LongText local intNote = tblLong.Item["NOTE2"] local tblNote = tblLong[intNote] if fhGetAppVersion() > 5 then -- FH V6 LongText variants -- V2.4/5 local intIndi = tblNote.Item["Individual"] table.insert(tblNote,intIndi ,"Witness Notes (~_SHAR.NOTE)") -- Insert before Individual item table.insert(tblNote,intIndi+1,"Witness Notes (~_SHAN.NOTE)") table.insert(tblNote,#tblNote-1,"Place Notes (_PLAC.NOTE)") -- Insert as prepenultimate item end if fhGetAppVersion() > 6 then -- FH V7 LongText variants -- V3.1 local intMedia = tblNote.Item["Multimedia"] tblNote[intMedia+2] = "Media Note (OBJE.NOTE)" -- Needs +2 due to inserted Witness Notes items tblNote[intMedia+3] = "Media Annotation (_NOTA)" table.insert(tblNote,#tblNote-1,"Source Template (_SRCT.NOTE)") -- Insert as prepenultimate NOTE2 items table.insert(tblNote,#tblNote-1,"Template Description (_SRCT.DESC)") table.insert(tblNote,#tblNote-1,"Metafield Description (~FDEF.DESC)") tblFilters.LongText[#tblFilters.LongText] = { Name="NOTE.TEXT"; Title="Note record Text fields"; -- Insert as penultimate LongText item Tip_T="Note && Research Note record Text (TEXT) fields"; Tip_L="Select a Text field type"; "Both record types"; "Note record Text (NOTE.TEXT)"; "Research Note (_RNOT.TEXT)"; } end tblFilters.Distinct = { -- Extra Filter Distinct toggles & combo drop lists -- Name of filter; Title & Tip for toggle and Tip & Items for drop list; { Name="SOUR.TITL"; Title="Record Names/Titles"; Tip_T="Source, Repository, Multimedia, Submitter Name/Title (ABBR/NAME/TITL) fields"; Tip_L="Select Record name/title type"; "All the Record types"; "Source Title (SOUR.TITL)"; "Source Short (SOUR.ABBR)"; "Repository (REPO.NAME)"; "Multimedia (OBJE.TITL)"; "Submitter (SUBM.NAME)"; }; { Name="age"; Title="Fact Age fields"; Tip_T="Fact Age (AGE) fields"; }; { Name="radAge_Mode" }; { Name="date"; Title=tblFilters.date.Title; Tip_T=tblFilters.date.Tip_T; Tip_L="Select a Date field type"; "All the Date fields"; "Fact Dates (~Fact.DATE)"; "Citation Entry (~DATA.DATE)"; "LMO/Sort Date (~OBJE._DATE)"; "Multimedia Date (OBJE._DATE)"; "LDS Ordination (~LDS.DATE)" }; -- LDS Ordination BAPL/CONL/ENDL/SLGC/SLGS.DATE fields -- V3.0 added LMO/Sort Date (~OBJE._DATE) { Name="radDateMode"; }; { Name="radPhraseMode"; }; -- V2.2 } setFilterItems(tblFilters.Distinct) -- V3.1 local tblDist = tblFilters.Distinct local intTitl = tblDist.Item["SOUR.TITL"] local tblTitl = tblDist[intTitl] if fhGetAppVersion() > 5 then -- FH V6 Distinct Record Names/Titles -- V2.4/5 table.insert(tblTitl,#tblTitl,"Place Name (_PLAC.TEXT)") -- Insert as penultimate SOUR.TITL item tblTitl.Tip_T = tblTitl.Tip_T:gsub("(Submitter.*TITL)","Place, and\n %1/TEXT") end if fhGetAppVersion() > 6 then -- FH V7 Distinct Record Names/Titles -- V3.1 local intMedia = tblTitl.Item["Multimedia"] tblTitl[intMedia] = "Multimedia (~FILE.TITL)" table.insert(tblTitl,#tblTitl,"Source Template (_SRCT.NAME)") -- Insert as penultimate SOUR.TITL item tblTitl.Tip_T = tblTitl.Tip_T:replace("Place","Place, Source Template") local intDate = tblDist.Item["date"] local tblDate = tblDist[intDate] local intLMO = tblDate.Item["LMO/Sort Date"] tblDate[intLMO] = "Local Media Date (~OBJE._DATE)" table.insert(tblDate,intLMO,"Source Metafield (~_FIELD.DATE)") end local function strComboTag(strTag) -- Combo drop list tag structure to ensure conformity, and structure must also be used in '2nd/3rd/4th... Item for drop list' above if strTag then return " ("..strTag..")" end -- Used by doPopulateCombo() to set tag, and setComboValues() to match tag return " %((.*)%)$" -- Used by setScopeMode(), saveOptions(), and btnSomeRecs:action() to extract tag end -- local function strComboTag local function doPopulateCombo(tblItem) -- Populate all combo drop list controls with items -- V2.1 local strName = tblItem.Name local iupList = tblCombo[strName] -- Obtain the iup.list control via its name iupList.AutoRedraw = "No" iupList.Sort = "No" for intTag = 1, #tblItem do -- Populate the combo drop list without Sort, as table items set order -- V2.3 iupList[intTag] = tblItem[intTag] end if tblItem.fhFunc then -- Populate the Scope/Attr list with appended FH Facts -- V2.3 local tblTag = tblItem.tblTag -- Dictionary & Array table of Fact Tags found -- V2.3 for intRec, strRec in ipairs ({"INDI";"FAM";}) do -- Find all Individual and Family Facts for combo drop list progbar.Step() -- Step progress bar -- V2.6 for ptrRec in iterate.Records(strRec) do for ptrFact in iterate.Facts(ptrRec) do if tblItem.fhFunc(ptrFact) then -- Event/Attribute fact found -- V2.3 local strTag = fhGetTag(ptrFact) if not tblTag[strTag] then -- Omit any Event/Attribute already found or excluded tblTag[strTag] = true table.insert(tblTag,fhCallBuiltInFunction("FactName",ptrFact)..strComboTag(strRec.."."..strTag)) end end end if progbar.Stop() then error("\n Plugin Aborted. ") end end end table.sort(tblTag) -- Sort the Facts alphabetically and append to the drop list for intTag = 1, #tblTag do iupList[ tonumber(iupList.Count) + 1 ] = tblTag[intTag] -- V3.1 end end iupList.AutoRedraw = "Yes" end -- local function doPopulateCombo local function setComboValues() -- Set values for combo drop list tags, called from btnDefault:action -- V2.1 for strName, iupList in pairs ( tblCombo ) do local strTag = tblLst[strName] or "" iupList.Value = 1 -- Default to 1st entry if strTag ~= "" then for intList = 2, iupList.Count do -- Search combo drop list items for matching tag if iupList[intList]:matches(strComboTag(strTag)) then iupList.Value = intList -- Select chosen entry break end end end end end -- local function setComboValues local function setToolTip(iupItem1,iupItem2) -- Refresh control tooltip otherwise it vanishes in XP! iupItem2 = iupItem2 or iupItem1 iupItem1.Tip = iupItem2.Tip end -- local function setToolTip local function iupToggle(tblItem,tblTab) -- Return toggle handle created from a filter table item above local strName = tblItem.Name -- V2.3 local strValue = tblTgl[strName] or "ON" local iupToggle = iup.toggle { Value=strValue; Title=tblItem.Title; Tip=tblItem.Tip_T; TipBalloon=iup_gui.Balloon; Expand="Yes"; action=function(self) setToolTip(self) end; } -- Refresh XP Tooltip -- V2.3 iupToggle.TipDelay = 6000 + ( #tblItem.Tip_T * 20 ) -- Display larger tooltips for longer -- V2.3 tblTab[strName] = iupToggle -- Save toggle handle in tblBasic or tblExtra return iupToggle end -- local function iupToggle local function iupList(tblItem) -- Return combo drop list handle created from a filter table item above local strName = tblItem.Name -- V2.3 local intCols = tblItem.Cols or 14 -- Search Scope is wider than default for Extra Filter drop lists -- V2.3 was 9 -- V3.1 is 14 local iupList = iup.list { DropDown="Yes"; VisibleColumns=intCols; Visible_Items=20; Tip=tblItem.Tip_L; TipBalloon=iup_gui.Balloon; Expand="Yes"; action=function(self) setToolTip(self) end; } -- Refresh XP Tooltip -- V2.3 tblCombo[strName] = iupList doPopulateCombo(tblItem) -- Populate combo drop list with names & tags from filter table item above return iupList end -- local function iupList local function doAppend(boxHandle,strEntry,...) -- Append controls to GUI box using a filter attributes entry above local arg = {...} -- V3.1 local intArg = 1 local tblTab = tblExtra if strEntry:match("Basic") then -- Choose tblBasic or tblExtra to save toggle handles tblTab = tblBasic end for intItem, tblItem in ipairs ( tblFilters[strEntry] ) do -- For each filter table entry: local iupItem = nil if #tblItem > 0 then -- Append toggle and combo drop list -- V2.1 -- V2.3 iupItem = iup.hbox { iupToggle(tblItem,tblTab); iupList(tblItem); Homogeneous="Yes"; Gap="1"; Margin="2x0"; } elseif tblItem.Title then -- V2.3 iupItem = iup.hbox { iupToggle(tblItem,tblTab); Margin="2x0"; }-- Append just a toggle else if arg[intArg] then iupItem = arg[intArg] -- Append another control if supplied intArg = intArg + 1 end end if iupItem then iup.Append(boxHandle,iupItem) end -- Append control to GUI box local strName = tblItem.Name -- V2.3 if tblBasic[strName] and -- Adjust a Basic Filter to 3 State toggle ( strName == "text" or #tblItem > 0 ) then -- if "text" toggle or associated Extra Filter has combo drop list -- V2.3 local iupToggle = tblBasic[strName] iupToggle["3State"] = "Yes" -- User can only set "ON"=1 or "OFF"=0 but Extra Filters tab can set "NOTDEF"=-1 iupToggle.action = function(self,intState) if intState < 0 then self.Value = "OFF" end setToolTip(self) end -- Refresh XP Tooltip end end end -- local function doAppend local function iupValue(strValue,tglA,tglB) -- Return toggle handle depending on toggle value if strValue == "OFF" then return tglB else return tglA end end -- local function iupValue local function iupRadio(strValue,tglA,tglB) -- Return radio handle created from two toggles return iup.radio { iup.hbox { tglA; tglB; Homogeneous="Yes"; }; Value=iupValue(strValue,tglA,tglB); } end -- local function iupRadio local function setAllToggles(strEntry,strMode) -- Set all Short/Long Text toggle options "ON"/"OFF" for intItem, tblItem in ipairs ( tblFilters[strEntry] ) do tblExtra[tblItem.Name].Value = strMode -- V2.3 if strMode == "ON" and tblCombo[tblItem.Name] then -- If toggle is ticked and combo drop list exists, -- V2.3 tblCombo[tblItem.Name].Value = 1 -- then reset combo drop list to default 1st entry end end end -- local function setAllToggles local function iupText(strVal) -- Return multiline text handle return iup.text { Value=strVal; MultiLine="Yes"; AutoHide="Yes"; Formatting="Yes"; AddFormatTag=iup.user { Bulk="Yes"; CleanOut="Yes"; }; } end -- local function iupText local intItem = ( tonumber(TblOption.Set["INDI"]) or 0 ) + ( tonumber(TblOption.Set["FAM"]) or 0 ) if intItem > 9000 then -- Progress Bar for doPopulateCombo() function use of fhFunc -- V2.6 progbar.Setup() progbar.Start("Initialisation",4) -- Initialisation Progress Bar, 2 x number of fhFunc in tblFilters above -- V2.6 progbar.Message("Finding Database Facts ~ Please Wait") end -- Create GUI controls local lblCommands = iup.label { Title=" This plugin is intended for those scenarios that cannot be handled by these commands: \n 'Edit > Find and Replace...' and 'Tools > Work with Data > Places...' or 'Addresses...' "; Alignment="ACENTER"; } -- V3.2 local lstSetScope = iupList ( tblFilters.SearchScope ) -- V2.3 local btnSomeRecs = iup.button { Title=" Select Records"; Padding="1x1"; Alignment=":ATOP"; } -- V2.8 -- V3.0 local lblSomeRecs = iup.label { Title="All possible records are selected "; } -- V2.8 -- V3.0 local frmSetScope = iup.frame { Title=" Search Scope "; iup.hbox { lstSetScope; btnSomeRecs; lblSomeRecs; Gap="14"; Margin="6x6"; }; } -- V3.0 Gap="19" -- V3.1 Gap="14" local boxBasicShort = iup.vbox { iup.hbox { Homogeneous="Yes"; }; } doAppend( boxBasicShort, "BasicShort" ) local boxBasicLong = iup.vbox { iup.hbox { Homogeneous="Yes"; }; } doAppend( boxBasicLong , "BasicLong" ) local frmBasicFilt = iup.frame { Title=" Basic Filters "; iup.hbox { boxBasicShort; boxBasicLong; Homogeneous="Yes"; Margin="6x2"; }; } local lblSeekText = iup.label { Title="Search: "; } local txtSeekText = iupText ( tblSet.Seek ) txtSeekText.valuechanged_cb=function(self) setToolTip(self,lblSeekText) doChange(self) end txtSeekText.caret_cb=function(self,lin,col,pos) doCaret(self,pos) end -- V3.0 local lblSwapText = iup.label { Title="Replace: "; } local txtSwapText = iupText ( tblSet.Swap ) txtSwapText.valuechanged_cb=function(self) setToolTip(self,lblSwapText) doChange(self) end txtSwapText.caret_cb=function(self,lin,col,pos) doCaret(self,pos) end -- V3.0 local tglTextPlain = iup.toggle { Title="Plain Text Mode"; } local tglTextPatt = iup.toggle { Title="LUA Pattern Mode"; } local radTextMode = iupRadio ( tblTgl.Text, tglTextPlain, tglTextPatt ) local tglCaseInse = iup.toggle { Title="Case Insensitive" ; action=function(self) setToolTip(self) end; } -- Refresh XP Tooltips local tglCaseSens = iup.toggle { Title="Case Sensistive" ; action=function(self) setToolTip(self) end; } local radCaseMode = iupRadio ( tblTgl.Case, tglCaseInse, tglCaseSens ) local tglWordWhole = iup.toggle { Title="Whole Words" ; action=function(self) setToolTip(self) end; } -- Refresh XP Tooltips local tglWordPart = iup.toggle { Title="Part Words" ; action=function(self) setToolTip(self) end; } local radWordMode = iupRadio ( tblTgl.Word, tglWordWhole, tglWordPart ) local tglWhSpHide = iup.toggle { Title="Hide White Space"; } -- V3.0 local tglWhSpShow = iup.toggle { Title="Show White Space"; } -- V3.0 local radWhSpMode = iupRadio ( tblTgl.WhSp, tglWhSpHide, tglWhSpShow ) local tglConfirm = iup.toggle { Title="Confirm the action for every item found"; Value=tblTgl.Conf or "ON"; } local boxOptLeft = iup.hbox { iup.vbox { lblSeekText; lblSwapText; }; iup.vbox { txtSeekText; txtSwapText; }; Margin="6x0"; } local boxOptRight = iup.vbox { radTextMode; radCaseMode; radWordMode; radWhSpMode; iup.hbox { tglConfirm; Margin="6x0"; }; Margin="6x2"; } -- V2.8 -- V3.0 local boxOptions = iup.hbox { boxOptLeft; boxOptRight; Homogeneous="Yes"; Margin="0x2"; } local frmOptions = iup.frame { Title=" Search Criteria "; boxOptions; } local lblSetPreset = iup.label { Title="Preset: "; } -- V2.5 local lstSetPreset = iup.list { Value=1; DropDown="Yes"; VisibleColumns=7; Visible_Items=20; } -- V2.5 local tglSetPreset = iup.toggle { Title="Lock"; action=function(self) setToolTip(self) end; } -- V2.5 local btnSetPreset = iup.button { Title="Manage Presets"; } -- V2.5 local boxSetPreset = iup.hbox { lblSetPreset; lstSetPreset; tglSetPreset; btnSetPreset; Gap="6"; Margin="6x6"; } -- V2.5 local frmSetPreset = iup.frame { Title=" Manage Presets "; boxSetPreset; } -- V2.5 local btnBasicRepl = iup.button { Title=" Search && Replace"; } local btnBasicSeek = iup.button { Title=" Search ONLY "; } local boxBasicBtn = iup.hbox { btnBasicRepl; btnBasicSeek; Homogeneous="Yes"; Gap="6"; Margin="6x6"; } -- V2.5 local frmBasicBtn = iup.frame { Title=" Search & Replace "; boxBasicBtn; } -- V2.5 local btnShortInc = iup.button { Title="Include All Short Text"; } local btnShortExc = iup.button { Title="Exclude All Short Text"; } local boxShortText = iup.vbox { iup.hbox { btnShortInc; btnShortExc; Homogeneous="Yes"; }; } local frmShortText = iup.frame { Title=" Short Text Fields "; boxShortText; } doAppend( boxShortText, "ShortText" ) local btnLongInc = iup.button { Title="Include All Long Text"; } local btnLongExc = iup.button { Title="Exclude All Long Text"; } local boxLongText = iup.vbox { iup.hbox { btnLongInc; btnLongExc; Homogeneous="Yes"; }; } local frmLongText = iup.frame { Title=" Long Text Fields "; boxLongText; } doAppend( boxLongText, "LongText" ) local tglAge_Inc = iup.toggle { Title="Age Warnings" ; action=function(self) setToolTip(self) end; TipBalloonTitle="BEWARE!"; TipBalloonTitleIcon="2"; } local tglAge_Exc = iup.toggle { Title="No Age Warnings" ; action=function(self) setToolTip(self) end } -- Refresh XP Tooltips local radAge_Mode = iupRadio ( tblTgl.Age, tglAge_Inc, tglAge_Exc ) local tglDateInc = iup.toggle { Title="Date Warnings" ; action=function(self) setToolTip(self) end; TipBalloonTitle="BEWARE!"; TipBalloonTitleIcon="2"; } local tglDateExc = iup.toggle { Title="No Date Warnings" ; action=function(self) setToolTip(self) end; } -- Refresh XP Tooltips local radDateMode = iupRadio ( tblTgl.Date, tglDateInc, tglDateExc ) local tglPhraseInc = iup.toggle { Title="Date Phrase Warnings" ; action=function(self) setToolTip(self) end; } -- V2.2 local tglPhraseExc = iup.toggle { Title="No Date Phrase Warnings" ; action=function(self) setToolTip(self) end; } -- Refresh XP Tooltips local radPhraseMode = iupRadio ( tblTgl.Phrase, tglPhraseInc, tglPhraseExc ) local boxDistinct = iup.vbox { } local frmDistinct = iup.frame { Title=" Distinctive Fields "; boxDistinct; } doAppend( boxDistinct, "Distinct", radAge_Mode, radDateMode, radPhraseMode ) -- V2.2 local btnExtraRepl = iup.button { Title=" Search && Replace"; } local btnExtraSeek = iup.button { Title=" Search ONLY "; } local boxExtraBtn = iup.hbox { btnExtraRepl; btnExtraSeek; Homogeneous="Yes"; Gap="6"; Margin="6x6"; } local frmExtraBtn = iup.frame { Title=" Search & Replace "; boxExtraBtn; } local btnDefault = iup.button { Title="Restore Defaults"; } local btnSetFont = iup.button { Title="Set Window Fonts"; } local btnGetHelp = iup.button { Title=" Help && Advice"; } local btnDestroy = iup.button { Title="Close Plugin"; action=function() doDestroy() return iup.CLOSE end; } local boxButtons = iup.hbox { btnDefault; btnSetFont; btnGetHelp; btnDestroy; Homogeneous="Yes"; Gap="5"; Margin="16x0"; } local boxMajor = iup.vbox { lblCommands; frmSetScope; frmBasicFilt; frmOptions; iup.hbox { frmSetPreset; frmBasicBtn; Homogeneous="Yes"; }; Margin="0"; } -- V2.5 -- V3.0 local boxExtra = iup.hbox { frmShortText; iup.vbox { frmLongText; frmDistinct; frmExtraBtn; }; Homogeneous="Yes"; Margin="0"; } -- V2.5 -- Create the Tab controls layout local tabControl = iup.tabs { boxMajor; TabTitle0=" Major Options "; boxExtra; TabTitle1=" Extra Filters "; } local dialogMain = iup.dialog { Title=iup_gui.Plugin..iup_gui.Version; Gap="1"; Margin="2x0"; iup.vbox { tabControl; boxButtons; }; } local tblConfirm = { ON = "Confirmation prompt for every item found"; OFF = "Process all matching text unconditionally"; } local tblTextTip = { ON = "Plain text"; OFF = "LUA pattern"; } -- V2.8 local tblActive = { ON = "Yes"; OFF = "No"; } -- V2.8 if fhGetAppVersion() > 6 then -- FH V7 IUP 3.28 -- V3.1 tabControl.TabPadding="8x4" else -- FH V6 IUP 3.11 -- V3.1 tabControl.Padding="8x4" end progbar.Close() -- Close Progress Bar of Initialiation for Finding Record Facts -- V2.6 local function setTabMode(intTab) -- Transfer toggles according to chosen tab called from setControls, saveOptions, tabControl:tabchangepos_cb intTab = intTab or IntTabPosn -- Default to current tab if intTab < 2 then local strTextValue = nil for strName, anyValue in pairs ( tblExtra ) do -- Transfer the Extra Filters to the Basic Filters local strExtraValue = tblExtra[strName].Value -- Extra Filter is "ON" or "OFF" if strExtraValue == "ON" and tblCombo[strName] and tblCombo[strName].Value > "1" then -- Extra Filter is "ON" and combo drop list selects subset strExtraValue = "NOTDEF" -- So Extra Filter is "NOTDEF" end if tblBasic[strName] and strName ~= "text" then -- Set each Basic Filter equal to Extra Filter (except "All other Text fields") tblBasic[strName].Value = strExtraValue else strTextValue = strTextValue or strExtraValue -- If other Extra Filters all "ON" or all "OFF" then use that value if strExtraValue ~= strTextValue then strTextValue = "NOTDEF" -- Otherwise, with a mixture, use "NOTDEF" value end end end tblBasic["text"].Value = strTextValue -- Set the Basic "All other Text fields" to this value elseif intTab == 2 then for strName, anyValue in pairs ( tblExtra ) do -- Transfer the Basic Filters to the Extra Filters local strBasicValue = tblBasic["text"].Value -- Default for Extra Filters uses Basic "All other Text fields" if tblBasic[strName] then strBasicValue = tblBasic[strName].Value -- But matching Extra Filter uses Basic Filter end if strBasicValue ~= "NOTDEF" then -- Unless the Basic Filter is "NOTDEF" tblExtra[strName].Value = strBasicValue if tblCombo[strName] and strBasicValue == "ON" then -- Filter is "ON" so reset combo drop list to 1st entry tblCombo[strName].Value = 1 end end end end end -- local function setTabMode local function setTextMode() -- Set Text Case & Word Modes called from setControls, tglTextPlain:action tglTextPlain.Tip = tglTextPlain.Tip tglTextPatt.Tip = tglTextPatt.Tip -- Refresh XP Tooltips radCaseMode.Active = tblActive[tglTextPlain.Value] -- Plain Text Mode enables Case & Word modes -- V2.8 radWordMode.Active = tblActive[tglTextPlain.Value] -- LUA Pattern Mode disables Case & Word modes -- V2.8 lblSeekText.Tip = tblTextTip[tglTextPlain.Value].." string to search for" txtSeekText.Tip = lblSeekText.Tip lblSwapText.Tip = tblTextTip[tglTextPlain.Value].." string to substitute" txtSwapText.Tip = lblSwapText.Tip end -- local function setTextMode local function setWhiteSpaceMode() -- Set Seek & Swap text White Space mode on/off -- V3.0 tglWhSpHide.Tip = tglWhSpHide.Tip tglWhSpShow.Tip = tglWhSpShow.Tip -- Refresh XP Tooltips tblTgl.WhSp = tglWhSpHide.Value if tglWhSpHide.Value == "ON" then txtSeekText.Value = strHideWhiteSpace(txtSeekText.Value) txtSwapText.Value = strHideWhiteSpace(txtSwapText.Value) else txtSeekText.Value = strShowWhiteSpace(txtSeekText.Value) txtSwapText.Value = strShowWhiteSpace(txtSwapText.Value) end end -- local function setWhiteSpaceMode local function setWarnMode() -- Set Age & Date Warning Mode called from setControls, saveOptions, tblExtra.age:action, tblExtra.date:action, tabControl:tabchangepos_cb tblExtra.age.Tip = tblExtra.age.Tip tblExtra.date.Tip = tblExtra.date.Tip -- Refresh XP Tooltips radAge_Mode.Active = tblActive[tblExtra.age.Value] -- Age Fields included/excluded enables/disables Warning mode -- V2.8 radDateMode.Active = tblActive[tblExtra.date.Value] -- Date Fields included/excluded enables/disables Warning mode -- V2.8 radPhraseMode.Active = tblActive[tblExtra.date.Value] end -- local function setWarnMode local dicRecName = { INDI="Individual (INDI)"; FAM="Family (FAM)"; NOTE="Note (NOTE)"; SOUR="Source (SOUR)"; REPO="Repository (REPO)"; OBJE="Multimedia (OBJE)"; _PLAC="Place (_PLAC)"; _RNOT="Research Note (_RNOT)"; _SRCT="Source Template (_SRCT)"; SUBM="Submitter (SUBM)"; SUBN="Submission (SUBN)"; } -- V3.1 local function setScopeMode() -- Set Search Scope called from setControls, lstSetScope:action, btnSomeRecs:action -- V2.8 local strSize = "All " local strType = "possible" local strText = " records are selected " if tblSet.Recs then strSize = tblSet.Recs.Size or "All " -- Number of Record Id selected if strSize == "1 " then strText = " record is selected " end end if lstSetScope.Value == "1" then btnSomeRecs.Active = "No" -- Set Select Records button inactive when all records chosen else btnSomeRecs.Active = "Yes" -- Set Select Records button active and identify record type strType = dicRecName[strScopeType(lstSetScope[lstSetScope.Value]:match(strComboTag()))] end lblSomeRecs.Title = strSize..strType..strText end -- local function setScopeMode local function setControls() -- Reset GUI control values called from lst/tgl/btnSetPreset:action, btnDefault:action local intCurr = tonumber(tblSet.Curr) if TblOption[intCurr] then CopyPreset(TblOption[intCurr],TblOption) -- Load the Preset database into current settings -- V2.5 end for intList, dicList in ipairs (tblSet.List) do lstSetPreset[intList] = dicList.Name -- Load the Preset database names into droplist -- V2.5 end local arrFrame = {frmOptions;frmBasicFilt;frmShortText;frmLongText;frmDistinct;btnDefault;} lstSetPreset.Value = intCurr tglSetPreset.Value = tblSet.List[intCurr].Lock or "OFF" -- Enable/disable frames depending on Preset Lock -- V2.5 if tglSetPreset.Value == "OFF" then for intFrame, iupFrame in ipairs (arrFrame) do iupFrame.Active = "Yes" end else for intFrame, iupFrame in ipairs (arrFrame) do iupFrame.Active = "No" end end txtSeekText.Value = tblVal.Seek txtSwapText.Value = tblVal.Swap txtSeekText.AddFormatTag = iup.user { Bulk="Yes"; CleanOut="Yes"; FgColor=iup_gui.Safe; } -- Special Multiline Formatted Text case!!! txtSwapText.AddFormatTag = iup.user { Bulk="Yes"; CleanOut="Yes"; FgColor=iup_gui.Safe; } tglConfirm.Value = tblTgl.Conf tglConfirm.Tip = tblConfirm[tglConfirm.Value] radTextMode.Value = iupValue(tblTgl.Text ,tglTextPlain,tglTextPatt) radCaseMode.Value = iupValue(tblTgl.Case ,tglCaseInse ,tglCaseSens) radWordMode.Value = iupValue(tblTgl.Word ,tglWordWhole,tglWordPart) radWhSpMode.Value = iupValue(tblTgl.WhSp ,tglWhSpHide ,tglWhSpShow) -- V3.0 radAge_Mode.Value = iupValue(tblTgl.Age ,tglAge_Inc ,tglAge_Exc ) radDateMode.Value = iupValue(tblTgl.Date ,tglDateInc ,tglDateExc ) radPhraseMode.Value= iupValue(tblTgl.Phrase,tglPhraseInc,tglPhraseExc) -- V2.2 for strName, iupValue in pairs ( tblExtra ) do tblExtra[strName].Value = tblTgl[strName] or "ON" end dialogMain:map() -- Needed to ensure Attributes combo drop list is sorted before setComboValues setComboValues() -- Set values for matching combo drop list tags -- V2.1 setTabMode() -- Set toggles for current tab, must come after setComboValues() setTextMode() -- Set Text Case & Word Modes depending on Plain Text/LUA Pattern mode setWhiteSpaceMode() -- Set Seek & Swap text white space symbols on or off -- V3.0 setWarnMode() -- Set Date & Age Warning mode depending on Date & Age toggle settings setScopeMode() -- Set Select Records mode and update records selected status -- V2.8 end -- local function setControls -- Assign GUI control attributes -- V2.1 local tblControls = { { "Font"; "FgColor"; "Expand"; "Tip", {"TipBalloon";"Balloon"}; { "help_cb";function() iup_gui.HelpDialogue(IntTabPosn) end }; "action"; setControls; }; [tabControl ] = { "FontHead"; "Head"; "Yes" ; "Select 'Major Options' or 'Extra Filters'"; }; [frmSetScope] = { "FontHead"; "Head"; "Horizontal"; }; -- V3.0 [boxBasicShort] = { "FontBody"; "Safe"; "Yes"; }; [boxBasicLong] = { "FontBody"; "Safe"; "Yes"; }; [frmBasicFilt] = { "FontHead"; "Head"; "Yes"; }; [boxOptions ] = { "FontBody"; "Safe"; "Yes"; }; [frmOptions ] = { "FontHead"; "Head"; "Yes"; }; [boxSetPreset] = { "FontBody"; "Safe"; "Yes"; }; -- V2.5 [frmSetPreset] = { "FontHead"; "Head"; "Horizontal"; }; -- V2.5 [boxBasicBtn] = { "FontBody"; "Safe"; "Yes"; }; -- V2.5 [frmBasicBtn] = { "FontHead"; "Head"; "Horizontal"; }; -- V2.5 [boxShortText] = { "FontBody"; "Safe"; "Yes"; }; [frmShortText] = { "FontHead"; "Head"; "Yes"; }; [boxLongText] = { "FontBody"; "Safe"; "Yes"; }; [frmLongText] = { "FontHead"; "Head"; "Yes"; }; [boxDistinct] = { "FontBody"; "Safe"; "Yes"; }; [frmDistinct] = { "FontHead"; "Head"; "Yes"; }; [boxExtraBtn] = { "FontBody"; "Safe"; "Yes"; }; -- V2.5 [frmExtraBtn] = { "FontHead"; "Head"; "Horizontal"; }; -- V2.5 [lblCommands] = { "FontHead"; "Risk"; "Yes" ; "The standard commands may offer a simpler solution than this plugin "; }; -- V3.2 [lstSetScope] = { "FontBody"; "Safe"; "Yes" ; "Set global search scope for Basic/Extra Filters"; }; -- V3.0 [btnSomeRecs] = { "FontBody"; "Safe"; "Horizontal"; "Select records to include in search\nwhere type matches Search Scope"; }; -- V2.8 [lblSomeRecs] = { "FontBody"; "Body"; "Yes" ; "Selected records to include in Search Scope"; }; -- V2.8 [lblSeekText] = { "FontBody"; "Body"; "Vertical" ; "Plain text string to search for"; }; [txtSeekText] = { "FontBody"; "Safe"; "Yes" ; "Plain text string to search for"; }; [lblSwapText] = { "FontBody"; "Body"; "Vertical" ; "Plain text string to substitute"; }; [txtSwapText] = { "FontBody"; "Safe"; "Yes" ; "Plain text string to substitute"; }; [tglTextPlain] = { "FontBody"; "Safe"; "Yes" ; "Plain text strings"; }; [tglTextPatt] = { "FontBody"; "Safe"; "Yes" ; "LUA pattern strings\nSee 'Help and Advice' for details"; }; [tglCaseInse] = { "FontBody"; "Safe"; "Yes" ; "Case insensitive text search"; }; [tglCaseSens] = { "FontBody"; "Safe"; "Yes" ; "Case sensitive text search"; }; [tglWordWhole] = { "FontBody"; "Safe"; "Yes" ; "Whole words are delimited by either\nspace, tab, newline, punctuation, or field edge"; }; [tglWordPart] = { "FontBody"; "Safe"; "Yes" ; "Part words may be any text"; }; [tglWhSpHide] = { "FontBody"; "Safe"; "Yes" ; "Hide white space symbols"; }; -- V3.0 [tglWhSpShow] = { "FontBody"; "Safe"; "Yes" ; "Show white space symbols"; }; -- V3.0 [tglConfirm ] = { "FontBody"; "Safe"; "Yes" ; tblConfirm[tglConfirm.Value] ; function() tglConfirm.Tip = tblConfirm[tglConfirm.Value] end; }; [lblSetPreset] = { "FontBody"; "Body"; "Vertical" ; "Choose the current Preset settings"; }; -- V2.5 [lstSetPreset] = { "FontBody"; "Safe"; "Yes" ; "Choose the current Preset settings"; }; -- V2.5 [tglSetPreset] = { "FontBody"; "Safe"; "Vertical" ; "Lock the Preset to prevent changes"; }; -- V2.5 [btnSetPreset] = { "FontBody"; "Safe"; "Yes" ; "Manage the Preset Settings database"; }; -- V2.5 [btnBasicRepl] = { "FontHead"; "Safe"; "Yes" ; "Start the Search and Replace process"; }; [btnBasicSeek] = { "FontHead"; "Safe"; "Yes" ; "Start the Search only process"; }; [btnShortInc] = { "FontBody"; "Safe"; "Yes" ; "Include all one line Short Text fields" ; function() setAllToggles("ShortText","ON") end; }; [btnShortExc] = { "FontBody"; "Risk"; "Yes" ; "Exclude all one line Short Text fields" ; function() setAllToggles("ShortText","OFF") end; }; [btnLongInc ] = { "FontBody"; "Safe"; "Yes" ; "Include all multi-line Long Text fields" ; function() setAllToggles("LongText","ON") end; }; [btnLongExc ] = { "FontBody"; "Risk"; "Yes" ; "Exclude all multi-line Long Text fields" ; function() setAllToggles("LongText","OFF") end; }; [tglAge_Inc ] = { "FontBody"; "Risk"; "Yes" ; "BEWARE: Affects record 'Updated' date\neven if the Age fields are skipped ! !"; }; [tglAge_Exc ] = { "FontBody"; "Safe"; "Yes" ; "Age Warnings disabled"; }; [tglDateInc ] = { "FontBody"; "Risk"; "Yes" ; "BEWARE: Affects record 'Updated' date\neven if the Date fields are skipped ! !"; }; [tglDateExc ] = { "FontBody"; "Safe"; "Yes" ; "Date Warnings disabled"; }; [tglPhraseInc] = { "FontBody"; "Risk"; "Yes" ; "Date Phrase Warnings enabled"; }; -- V2.2 [tglPhraseExc] = { "FontBody"; "Safe"; "Yes" ; "Date Phrase Warnings disabled"; }; -- V2.2 [btnExtraRepl] = { "FontHead"; "Safe"; "Yes" ; "Start the Search and Replace process"; }; [btnExtraSeek] = { "FontHead"; "Safe"; "Yes" ; "Start the Search only process"; }; [btnDefault ] = { "FontBody"; "Safe"; "Horizontal"; "Restore default Settings for Options and Window positions and sizes"; }; [btnSetFont ] = { "FontBody"; "Safe"; "Horizontal"; "Choose user interface window font style"; }; [btnGetHelp ] = { "FontBody"; "Safe"; "Horizontal"; "Obtain online Help and Advice from the Plugin Store"; }; [btnDestroy ] = { "FontBody"; "Risk"; "Horizontal"; "Close the Plugin"; }; } iup_gui.AssignAttributes(tblControls) -- Assign GUI control attributes -- V2.1 local function saveOptions() -- Save all GUI settings called from doSearchReplace, doSearchOnly, lst/tgl/btnSetPreset:action, btnSetFont:action, btnDestroy:action if IntTabPosn < 2 then setTabMode(2) -- Transfer Basic Filters to Extra Filters if necessary setWarnMode() end for strName, anyValue in pairs ( tblTgl ) do if not tblExtra[strName] then tblTgl[strName] = nil -- Clear the non-GUI Class and Tag toggles end end for strName, anyValue in pairs ( tblExtra ) do tblTgl[strName] = tblExtra[strName].Value -- Save all the GUI Class and Tag and "Attr" toggle filters end for strName, anyValue in pairs ( tblCombo ) do local strText = tblCombo[strName][tblCombo[strName].Value] tblLst[strName] = strText:match(strComboTag()) or "" -- Save all the GUI combo drop list tag filters end tblVal.Seek = strHideWhiteSpace(txtSeekText.Value) -- Save text strings -- V2.5 -- Hide white space symbols -- V3.0 tblVal.Swap = strHideWhiteSpace(txtSwapText.Value) tblSet.Mode = nil -- Mode and Data Ref are not saved tblSet.Data = nil tblTgl.Conf = tglConfirm.Value -- Save all mode toggles tblTgl.Text = tglTextPlain.Value tblTgl.Case = tglCaseInse.Value tblTgl.Word = tglWordWhole.Value tblTgl.WhSp = tglWhSpHide.Value -- V3.0 tblTgl.Age = tglAge_Inc.Value tblTgl.Date = tglDateInc.Value tblTgl.Phrase= tglPhraseInc.Value -- V2.2 local intCurr = tonumber(tblSet.Curr) tblSet.List[intCurr].Lock = tglSetPreset.Value if not TblOption[intCurr] then TblOption[intCurr] = {} end -- V2.5 CopyPreset(TblOption,TblOption[intCurr]) -- Save the Preset database from current settings -- V2.5 SaveSettings() -- Save sticky data settings end -- local function saveOptions function tglTextPlain:action(intState) -- Action for Plain Text Mode v LUA Pattern Mode radio toggle setTextMode() end -- function tglTextPlain:action function tglWhSpHide:action(intState) -- Action for Hide/Show White Space symbols radio toggle -- V3.0 setWhiteSpaceMode() end -- function tglWhSpHide:action function lstSetScope:action(strText,intItem,intState) -- Action for Search Scope drop list -- V2.8 lstSetScope.Tip = lstSetScope.Tip -- Refresh XP Tooltip if intState == 1 then tblSet.Recs = nil -- Enable all Record Id setScopeMode() -- Make the Select Records button active depending on Search Scope droplist end end -- lstSetScope:action function btnSomeRecs:action() -- Action for Select Records button -- V2.8 dialogMain.Active="No" local strType = strScopeType(lstSetScope[lstSetScope.Value]:match(strComboTag())) or "INDI" local tblRecs = fhPromptUserForRecordSel(strType) -- Select Record Id for Search Scope record type tag tblSet.Recs = nil -- Enable all Record Id if #tblRecs > 0 then tblSet.Recs = {} tblSet.Recs.Size = tostring(#tblRecs).." " -- Save number of Record Id selected for intRec, ptrRec in ipairs (tblRecs) do tblSet.Recs[fhGetRecordId(ptrRec)] = true -- Tabulate each Record Id selected end end setScopeMode() -- Update status message of records selected dialogMain.Active="Yes" dialogMain.BringFront="Yes" end -- function btnSomeRecs:action function tblExtra.age:action(intState) -- Action for Age fields toggle setWarnMode() end -- function tblExtra.age:action function tblExtra.date:action(intState) -- Action for Date fields toggle setWarnMode() end -- function tblExtra.date:action local function doPerformAction() -- Perform action for both Search & Replace and Search ONLY button if tblVal.Seek:match("\n") or tblVal.Swap:match("\n") then -- Check newline characters are only used with Long Text fields -- V2.1 local strToggle = "OFF" for intItem, tblItem in ipairs ( tblFilters.ShortText ) do if tblTgl[tblItem.Name] == "ON" then -- Must check before true/false adjustment below as main toggle may get disabled -- V2.4 strToggle = "ON" -- Short Text option selected break end end if strToggle == "ON" -- Short Text or Record Title or Age or Date selected? or tblTgl["SOUR.TITL"] == "ON" -- Source Title is Long Text, but others are Short Text, and Source Title unlikely to have newlines or tblTgl.age == "ON" or tblTgl.date == "ON" then iup_gui.MemoDialogue("\n Newline characters in Search &&/or Replace text \n are reserved exclusively for Long Text Fields, \n such as Note, Fact Address, Text From Source. \n") return false end end for strName, strValue in pairs ( tblTgl ) do -- TblOptions can be altered as SaveSettings() is not now called except via saveOptions() tblTgl[strName] = ( strValue == "ON" ) -- Swap "ON" to true, and "OFF" to false, in all toggle options end for strSource, tblTarget in pairs ( tblRelated ) do for _, strTarget in ipairs ( tblTarget ) do -- Settings for related target Classes & Tags from source options tblTgl[strTarget] = tblTgl[strSource] end end for strName, anyValue in pairs ( tblCombo ) do -- Adjust true/false settings for related combo drop list tags if tblTgl[strName] and tblLst[strName] ~= "" then -- Matching extra toggle option is ticked and combo drop list names a tag local strTag = tblLst[strName] strTag = strTag:replace(".NOTE",".NOTE2") strTag = strTag:replace("~OBJE","~OBJE2") tblTgl[strName] = ( strName == strTag ) for _, strTarget in ipairs ( tblRelated[strName] or {} ) do tblTgl[strTarget] = ( strTarget == strTag ) end tblTgl[strTag] = true end end if tblTgl.Text then -- Plain Text mode -- 1 Jan 2014 tblTgl.Text = " Plain Text " if tblVal.Seek:match("^["..StrSP.."]+$") then -- V2.1 Exclude seek for white space &/or punctuation from Whole Word search tblTgl.Word = false end if tblTgl.Word -- Whole Word mode -- 1 Jan 2014 and tblVal.Seek ~= "" then -- Except empty search string -- 5 Jul 2014 V2.3 local strDelimit = "(["..StrSP.."\02])" -- Replace whole words delimited by white space or punctuation or STX="\02" characters tblVal.Seek = tblVal.Seek:gsub("(["..StrSP.."])","%1\02"):plain() -- !! tblVal.Swap = "%1"..tblVal.Swap:plain().."%2" tblVal.Swap = "%1"..tblVal.Swap:inert().."%2" tblVal.Seek = strDelimit..tblVal.Seek..strDelimit -- Add word delimiters else tblVal.Seek = tblVal.Seek:plain() -- Hide any LUA pattern magic characters -- !! tblVal.Swap = tblVal.Swap:plain() tblVal.Swap = tblVal.Swap:inert() end if tblTgl.Case then -- Case Insensitive mode tblVal.Seek = tblVal.Seek:caseless() end else tblTgl.Text = " LUA Pattern " -- LUA Pattern search mode tblTgl.Case = false -- Must disable Case Insensitive and Whole Word searches tblTgl.Word = false end if tblVal.Seek == "" then tblVal.Seek = "^$" -- Empty search string must use empty LUA pattern -- V2.0 tblTgl.Word = false -- and disable Whole Word search mode end if tblVal.Swap == "" then -- Change to any char < "\07" as it cannot be enterd by user even via Alt+001 tblVal.Swap = "\01" -- Use SOH="\01" character so DoSeekMatch() always detects a change -- V2.0 end local tblUser = iup_gui.DialogueAttributes("Main") -- Customise the Replace dialogue to use Main dialogue size & position iup_gui.CustomDialogue("User",tblUser.Raster,tblUser.CoordX,tblUser.CoordY) DoAllItems() -- Perform the Search and optional Replace process return true end -- local function doPerformAction local function doOutputResultSet(strTitle,strSub,strOldVal,strNewVal) -- Output the Result Set details -- V3.1 if #TblRecord == 0 then -- No matching data, so report and clear Result Set local strMode = "reported" if strNewVal then strMode = "replaced" end iup_gui.MemoDialogue("\n No matching data items have been "..strMode..". \n") TblResultSet = { Title=strTitle; Sub=strSub; Old=strOldVal; New=strNewVal; } return false end TblResultSet = {} fhOutputResultSetTitles(strTitle..strSub, strTitle, strSub.." Date: %#x") fhOutputResultSetColumn("Record Name", "item", TblRecord , #TblRecord, 180, "align_left") fhOutputResultSetColumn("Data Ref" , "text", TblDataRef , #TblRecord, 150, "align_left") fhOutputResultSetColumn("Data Ref" , "item", TblRefValue, #TblRecord, 250, "align_left", 0, true, "default", "buddy") fhOutputResultSetColumn( strOldVal , "text", TblOldValue, #TblRecord, 250, "align_left") if strNewVal then fhOutputResultSetColumn( strNewVal , "text", TblNewValue, #TblRecord, 250, "align_left") -- Only for replacement end fhOutputResultSetColumn("Ref Value" , "item", TblRefValue, #TblRecord, 250, "align_left", 0, true, "default", "buddy") return true end -- local function doOutputResultSet local function doSearchReplace() -- Search & Replace action saveOptions() local strSeek = tblVal.Seek local strSwap = tblVal.Swap tblSet.Mode = "Replace" if doPerformAction() then if not TblOption.Tgl.WhSp then -- V3.1 strSeek = strShowWhiteSpace(strSeek) strSwap = strShowWhiteSpace(strSwap) end local strTitle = "Search and Replace Results " -- Output Result Set for replacements -- V3.1 local strSubTitle = "Replacing"..tblTgl.Text..strSeek.." with "..strSwap if doOutputResultSet(strTitle,strSubTitle,"Old Value","New Value") then return iup.CLOSE end end end -- local function doSearchReplace function btnBasicRepl:action() -- Action for Basic Search & Replace button -- V2.1 return doSearchReplace() end -- function btnBasicRepl:action function btnExtraRepl:action() -- Action for Extra Search & Replace button return doSearchReplace() end -- function btnExtraRepl:action local function doSearchOnly() -- Search Only action saveOptions() local strSeek = tblVal.Seek tblVal.Swap = "\03" -- Use ETX="\03" character so DoSeekMatch() always detects a change -- 1 Jan 2014 tblSet.Mode = "Report" if doPerformAction() then if not TblOption.Tgl.WhSp then -- V3.1 strSeek = strShowWhiteSpace(strSeek) end local strTitle = "Search Results " -- Output Result Set for reported text -- V3.1 local strSubTitle = "Searching for"..tblTgl.Text..strSeek if doOutputResultSet(strTitle,strSubTitle,"Data Value") then return iup.CLOSE end end end -- local function doSearchOnly function btnBasicSeek:action() -- Action for Basic Search ONLY button -- V2.1 return doSearchOnly() end -- function btnBasicSeek:action function btnExtraSeek:action() -- Action for Extra Search ONLY button return doSearchOnly() end -- function btnExtraSeek:action function lstSetPreset:action(strCurr,intCurr,intState) -- Action for Set Preset droplist -- V2.5 tblSet.Curr = intCurr tglSetPreset.Value = tblSet.List[intCurr].Lock or "OFF" if intState == 0 then saveOptions() -- Save current settings in old Preset database -- V2.5 else if not TblOption[intCurr].Val then UserDefaultOptions(TblOption[intCurr]) -- Create default database for undefined Preset -- V2.5 end setControls() -- Load new Preset database into current settings -- V2.5 end setToolTip(lstSetPreset) -- Refresh XP Tooltip -- V2.3 end -- function lstSetPreset function tglSetPreset:action(intState) -- Action for Lock preset toggle -- V2.5 saveOptions() setControls() setToolTip(tglSetPreset) -- Refresh XP Tooltip -- V2.3 end -- function tglSetPreset:action function btnSetPreset:action() -- Action for Manage Presets button -- V2.5 saveOptions() ManagePresets() lstSetPreset[#tblSet.List+1] = iup.NULL -- Curtail shortened Preset dropdown list setControls() -- Load new Preset database into current settings -- V2.5 end -- function btnSetPreset:action function btnDefault:action() -- Action for Restore Defaults button ResetDefaultSettings() setControls() -- Reset controls & redisplay Main dialogue iup_gui.ShowDialogue("Main") SaveSettings() -- Save sticky data settings end -- function btnDefault:action function btnSetFont:action() -- Action for Set Window Font button -- V2.1 btnSetFont.Active = "No" saveOptions() iup_gui.FontDialogue(tblControls) SaveSettings() -- Save sticky data settings btnSetFont.Active = "Yes" end -- function btnFontSet: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/search-and-replace" local arrHelp = { "-major-options-tab"; "-extra-filters-tab"; } function btnGetHelp:action() -- Action for Help & Advice button according to current tab local strPage = arrHelp[IntTabPosn] or "" doExecute( strHelp..strPage ) fhSleep(3000,500) dialogMain.BringFront="YES" end -- function btnGetHelp:action function doDestroy() -- Close Plugin actions -- V3.1 saveOptions() if TblResultSet.Title then -- Clear the Result Set TblRecord[1] = fhNewItemPtr() TblDataRef[1] = "" TblOldValue[1] = "No matching data items" TblNewValue[1] = "" TblRefValue[1] = fhNewItemPtr() doOutputResultSet(TblResultSet.Title,TblResultSet.Sub,TblResultSet.Old,TblResultSet.New) end end -- function doDestroy --[=[ function btnDestroy:action() -- Action for Close Plugin button -- V3.1 doDestroy() return iup.CLOSE end -- function btnDestroy:action --]=] function dialogMain:close_cb() -- Close X button callback action -- V3.1 doDestroy() end -- function dialogMain:close_cb function tabControl:tabchangepos_cb(intNew,intOld) -- Call back when Main tab position is changed IntTabPosn = intNew + 1 setTabMode() -- Set toggles for current tab setWarnMode() -- saveOptions() -- Could save IntTabPosn sticky setting ? end -- function tabControl:tabchangepos_cb iup_gui.ShowDialogue("Main",dialogMain,btnDestroy) end -- function GUI_MainDialogue -- Main Code Section Starts Here -- fhInitialise(5,0,8,"save_recommended") -- 5.0.8 for Project/User/Machine Plugin Data needed for Presets PresetGlobalData() -- Preset global data definitions ResetDefaultSettings() -- Preset default sticky settings LoadSettings() -- Load sticky data settings iup_gui.CheckVersionInStore() -- Notify if later Version GUI_MainDialogue() -- Invoke graphical user interface

Source:Search-and-Replace-4.fh_lua