Map Life Facts.fh_lua
--[[
@Title: Map Life Facts
@Type: Standard
@Author: Mike Tate
@Contributors:
@Version: 6.1
@Keywords:
@LastUpdated: 08 Oct 2026
@Licence: This plugin is copyright (c) 2026 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: Creates maps for web pages by using online mapping tools to display Locations for each persons Facts.
@Version Log: See end of file.
]]
if fhGetAppVersion() > 5 then fhSetStringEncoding("UTF-8") end
--[[
@Title: aa Library Functions Preamble
@Author: Mike Tate
@Version: 4.1
@LastUpdated: 18 Feb 2026
@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+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.5
@LastUpdated: 12 Dec 2024
@Description: A general functions module to supplement LUA functions, where filenames use UTF-8 but for a few exceptions.
@V3.5: Further fix for Crossover/WINE file attributes;
@V3.4: Further fix for Unix/WINE file attributes;
@V3.3: Fix problem in fh.MakeFolder(...) when folder path is invalid; Remove Type, ShortName & ShortPath from attributes(...) as unsupported in WINE;
@V3.2: Added function DetectOldModules(); Updated functions RenameFile(), RenameFolder() & GetFolderContents();
@V3.1: Functions derived from FH V7 fhFileUtils library using File System Objects, plus additional features;
@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("luacom") -- To create File System Object
fh.FSO = luacom.CreateObject("Scripting.FileSystemObject")
-- Report error message --
local function doError(strMessage,errFunction)
-- strMessage ~ error message text
-- errFunction ~ optional error reporting function
if type(errFunction) == "function" then
errFunction(strMessage)
else
error(strMessage)
end
end -- local function doError
-- Convert filename to ANSI alternative and indicate success --
function fh.FileNameToANSI(strFileName,strAnsiName)
-- strFileName ~ full file path
-- strAnsiFile ~ ANSI file name & type
-- return values ~ ANSI file path, true if original path was ANSI compatible
if stringx.encoding() == "ANSI" then return strFileName, true end
local isFlag = fhIsConversionLossFlagSet()
fhSetConversionLossFlag(false)
local strAnsi = fhConvertUTF8toANSI(strFileName)
local wasAnsi = true
if fhIsConversionLossFlagSet() then
strAnsiName = strAnsiName or "ANSI.ANSI"
strAnsi = fhGetContextInfo("CI_APP_DATA_FOLDER").."\\Plugin Data\\"..strAnsiName
wasAnsi = false
end
fhSetConversionLossFlag(isFlag)
return strAnsi, wasAnsi
end -- local function FileNameToANSI
-- Get parent folder --
function fh.GetParentFolder(strFileName)
-- strFileName ~ full file path
-- return value ~ parent folder path
local strParent = fh.FSO:GetParentFolderName(strFileName) --! Faulty in FH v6 with Unicode chars in path
if fhGetAppVersion() == 6 then
local _, wasAnsi = fh.FileNameToANSI(strFileName)
if not wasAnsi then
strParent = strFileName:match("^(.+)[\\/][^\\/]+[\\/]?$")
end
end
return strParent
end -- function GetParentFolder
-- Check if file exists --
function fh.FlgFileExists(strFileName)
-- strFileName ~ full file path
-- return value ~ true if it exists
return fh.FSO:FileExists(strFileName)
end -- function FlgFileExists
-- Check if folder exists --
function fh.FlgFolderExists(strFolderName)
-- strFolderName ~ full file path
-- return value ~ true if it exists
return fh.FSO:FolderExists(strFolderName)
end -- function FlgFolderExists
-- Delete a file if it exists --
function fh.DeleteFile(strFileName,errFunction)
-- strFileName ~ full file path
-- errFunction ~ optional error reporting function
-- return value ~ true if file does not exist or is deleted else false
if fh.FSO:FileExists(strFileName) then
fh.FSO:DeleteFile(strFileName,true)
if fh.FSO:FileExists(strFileName) then
doError("File Not Deleted:\n"..strFileName.."\n",errFunction)
return false
end
end
return true
end -- function DeleteFile
-- Delete a folder if it exists including contents --
function fh.DeleteFolder(strFolderName,errFunction)
-- strFolderName ~ full folder path
-- errFunction ~ optional error reporting function
-- return value ~ true if folder does not exist or is deleted else false
if fh.FSO:FolderExists(strFolderName) then
fh.FSO:DeleteFolder(strFolderName,true)
if fh.FSO:FolderExists(strFolderName) then
doError("Folder Not Deleted:\n"..strFolderName.."\n",errFunction)
return false
end
end
return true
end -- function DeleteFolder
-- Rename a file if it exists --
function fh.RenameFile(strFileName,strNewName)
-- strFileName ~ full file path
-- strNewName ~ new file name & type
-- return value ~ true if file exists but new name does not and rename is OK else false
local strNewFile = fh.GetParentFolder(strFileName).."\\"..strNewName
if fh.FSO:FileExists(strFileName) and not fh.FSO:FileExists(strNewFile) then
local fileObject = fh.FSO:GetFile(strFileName)
fileObject.Name = strNewName
if fh.FSO:FileExists(strNewFile) then
return true
end
end
return false
end -- function RenameFile
-- Rename a folder if it exists --
function fh.RenameFolder(strFolderName,strNewName)
-- strFolderName ~ full folder path
-- strNewName ~ new folder name
-- return value ~ true if folder exists but new name does not and rename is OK else false
local strNewFolder = fh.GetParentFolder(strFolderName).."\\"..strNewName
if fh.FSO:FolderExists(strFolderName) and not fh.FSO:FolderExists(strNewFolder) then
local folderObject = fh.FSO:GetFolder(strFolderName)
folderObject.Name = strNewName
if fh.FSO:FolderExists(strNewFolder) then
return true
end
end
return false
end -- function RenameFolder
-- Copy a file if it exists and destination is not a folder --
function fh.CopyFile(strFileName,strDestination)
-- strFileName ~ full source file path
-- strDestination ~ full target file path
-- return value ~ true if file exists and is copied else false
if fh.MakeFolder(fh.GetParentFolder(strDestination)) and fh.FSO:FileExists(strFileName) and not fh.FSO:FolderExists(strDestination) then
fh.FSO:CopyFile(strFileName,strDestination)
if fh.FSO:FileExists(strDestination) then
return true
end
end
return false
end -- function CopyFile
-- Copy a folder if it exists and destination is not a file --
function fh.CopyFolder(strFolderName,strDestination)
-- strFolderName ~ full source folder path
-- strDestination ~ full target folder path
-- return value ~ true if folder exists and is copied else false
if fh.MakeFolder(fh.GetParentFolder(strDestination)) and fh.FSO:FolderExists(strFolderName) and not fh.FSO:FileExists(strDestination) then
fh.FSO:CopyFolder(strFolderName,strDestination)
if fh.FSO:FolderExists(strDestination) then
return true
end
end
return false
end -- function CopyFolder
-- Move a file if it exists and destination is not a folder --
function fh.MoveFile(strFileName,strDestination)
-- strFileName ~ full source file path
-- strDestination ~ full target file path
-- return value ~ true if file exists and is moved else false
if fh.MakeFolder(fh.GetParentFolder(strDestination)) and fh.FSO:FileExists(strFileName) and not fh.FSO:FolderExists(strDestination) then
if fh.DeleteFile(strDestination) then
fh.FSO:MoveFile(strFileName,strDestination)
if fh.FSO:FileExists(strDestination) then
return true
end
end
end
return false
end -- function MoveFile
-- Move a folder if it exists and destination is not a file --
function fh.MoveFolder(strFolderName,strDestination)
-- strFolderName ~ full source folder path
-- strDestination ~ full target folder path
-- return value ~ true if folder exists and is moved else false
if fh.MakeFolder(fh.GetParentFolder(strDestination)) and fh.FSO:FolderExists(strFolderName) and not fh.FSO:FileExists(strDestination) then
if fh.DeleteFolder(strDestination) then
fh.FSO:MoveFolder(strFolderName,strDestination)
if fh.FSO:FolderExists(strDestination) then
return true
end
end
end
return false
end -- function MoveFolder
local function CreateFolder(strFolderName) -- V3.3
fh.FSO:CreateFolder(strFolderName)
end -- local function CreateFolder(strFolderName)
-- Make subfolder recursively if does not exist --
function fh.MakeFolder(strFolderName,errFunction)
-- strFolderName ~ full source folder path
-- errFunction ~ optional error reporting function
-- return value ~ true if folder exists or created else false
if not fh.FSO:FolderExists(strFolderName) then
if #strFolderName > 4 -- V3.3
and not fh.MakeFolder(fh.GetParentFolder(strFolderName),errFunction) then
return false
end
if not pcall(CreateFolder,strFolderName) -- V3.3
or not fh.FSO:FolderExists(strFolderName) then
doError("Cannot Make Folder Path: \n"..strFolderName.." \n",errFunction)
return false
end
end
return true
end -- function MakeFolder
-- Check if folder writable --
function fh.FlgFolderWrite(strFolderName)
-- strFolderName ~ full source folder path
-- return value ~ true if folder writable else false
if fh.FlgFolderExists(strFolderName) then
if fh.MakeFolder(strFolderName.."\\vwxyz") then
fh.FSO:DeleteFolder(strFolderName.."\\vwxyz",true)
return true
end
end
return false
end -- function FlgFolderWrite
-- Open File with ANSI path and return Handle --
function fh.OpenFile(strFileName,strMode)
-- strFileName ~ full file path
-- strMode ~ "r", "w", "a" optionally suffixed with "+" &/or "b"
-- return value ~ file handle
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(strContents,strFileName,strFormat)
-- strContents ~ text string
-- strFileName ~ full file path
-- strFormat ~ optional "UTF-8" or "UTF-16LE"
-- return value ~ true if successful else false
strFormat = strFormat or "UTF-8"
if fhGetAppVersion() > 6 then
return fhSaveTextFile(strFileName,strContents,strFormat)
end
local strAnsi, wasAnsi = fh.FileNameToANSI(strFileName)
local fileHandle = fh.OpenFile(strAnsi,"w")
fileHandle:write(strContents)
assert(fileHandle:close())
if not wasAnsi then
fh.MoveFile(strAnsi,strFileName)
end
return true
end -- function SaveStringToFile
-- Load string from file --
function fh.StrLoadFromFile(strFileName,strFormat)
-- strFileName ~ full file path
-- strFormat ~ optional "UTF-8" or "UTF-16LE"
-- return value ~ file contents
strFormat = strFormat or "UTF-8"
if fhGetAppVersion() > 6 then
return fhLoadTextFile(strFileName,strFormat)
end
local strAnsi, wasAnsi = fh.FileNameToANSI(strFileName)
if not wasAnsi then
fh.CopyFile(strFileName,strAnsi)
end
local fileHandle = fh.OpenFile(strAnsi,"r")
local strContents = fileHandle:read("*all")
assert(fileHandle:close())
return strContents
end -- function StrLoadFromFile
-- Returns the Path, Filename, and Extension as 3 values --
function fh.SplitFilename(strFileName)
-- strFileName ~ full file path
-- return values ~ path, name.type, type
if fh.FSO:FolderExists(strFileName) then
local strPath = strFileName:gsub("[\\/]$","")
return strPath.."\\","",""
end
strFileName = strFileName.."."
return strFileName:match("^(.-)([^\\/]-%.([^\\/%.]-))%.?$")
end -- function SplitFilename
-- Convert dd/mm/yyyy hh:mm:ss format to integer seconds -- (DateTime format is used in attributes returned by GetFolderContents and DirTree below)
function fh.IntTime(strDateTime)
-- strDateTime ~ date time string
-- return value ~ integer seconds since 01/01/1970 00:00:00
local strDay,strMonth,strYear,strHour,strMin,strSec = strDateTime:match("^(%d%d)/(%d%d)/(%d+) (%d%d):(%d%d):(%d%d)")
if tonumber(strYear) < 1970 then return 0 end
local isDST = false
if tonumber(strMonth) > 4 and tonumber(strMonth) < 11 then isDST = true end -- Approximation is sometimes wrong
local intTime = os.time( { year=strYear; month=strMonth; day=strDay; hour=strHour; min=strMin; sec=strSec; isdst=isDST; } )
local tblDat = os.date("*t",intTime)
if tblDat.isdst then
intTime = intTime + 3600
isDST = true
end
return intTime
end -- function IntTime
-- Return table of attributes for folder --
local function attribFolder(tblAttr)
-- tblAttr ~ folder attributes table
-- return value ~ attributes table like LFS except datetimes
-- WINE only supports the tblAttr.name & tblAttr.path
return { mode="directory"; name=tblAttr.name; path=tblAttr.path; }
end -- local function attribFolder
-- Return table of attributes for file --
local function attribFile(tblAttr)
-- tblAttr ~ file attributes table
-- return value ~ attributes table like LFS except datetimes
-- WINE does not support the tblAttr.type, tblAttr.shortname, tblAttr.shortpath & sometimes tblAttr.datecreated
return { mode="file"; name=tblAttr.name; path=tblAttr.path; size=tblAttr.size; modified=tblAttr.datelastmodified; attributes=tblAttr.attributes; }
end -- local function attribFile
-- Return attributes table of all files and folders in a specified folder --
function fh.GetFolderContents(strFolder,doRecurse)
-- strFolder ~ full folder path
-- doRecurse ~ true for recursion
-- return value ~ attributes table
local arrList = {}
if fh.FSO:FolderExists(strFolder) then
local function getFileList(strFolder)
local tblList = fh.FSO:GetFolder(strFolder)
local tblEnum = luacom.GetEnumerator(tblList.SubFolders)
local tblAttr = tblEnum:Next()
while tblAttr do
table.insert(arrList,attribFolder(tblAttr))
if doRecurse then getFileList(tblAttr.path) end
tblAttr = tblEnum:Next()
end
local tblEnum = luacom.GetEnumerator(tblList.Files)
local tblAttr = tblEnum:Next()
while tblAttr do
table.insert(arrList,attribFile(tblAttr))
tblAttr = tblEnum:Next()
end
end
getFileList(strFolder)
end
return arrList
end -- function GetFolderContents
-- Return a Directory Tree entry & attributes on each iteration --
function fh.DirTree(strDir,...)
-- strDir ~ full folder path
-- ... ~ list of folders to omit
-- return value ~ full path, attributes table
local arg = {...}
assert( fh.FSO:FolderExists(strDir), "directory parameter is missing or empty" )
local function yieldtree(strDir)
local tblList = fh.FSO:GetFolder(strDir)
local tblEnum = luacom.GetEnumerator(tblList.SubFolders)
local tblAttr = tblEnum:Next()
while tblAttr do -- for _,tblAttr in luacom.pairs(tblList.SubFolders) do -- pairs not working in FH v6 so use tblEnum code
coroutine.yield(tblAttr.path,attributes(tblAttr,"directory"))
local isOK = true
for _,strOmit in ipairs (arg) do
if tblAttr.path:match(strOmit) then -- Omit tree branch
isOK = false
break
end
end
if isOK then yieldtree(tblAttr.path) end
tblAttr = tblEnum:Next()
end
local tblEnum = luacom.GetEnumerator(tblList.Files)
local tblAttr = tblEnum:Next()
while tblAttr do -- for _,tblAttr in luacom.pairs(tblList.Files) do -- pairs not working in FH v6 so use tblEnum code
coroutine.yield(tblAttr.path,attributes(tblAttr,"file"))
tblAttr = tblEnum:Next()
end
end
return coroutine.wrap(function() yieldtree(strDir) end)
end -- function DirTree
-- Detect FH V5/6 old library modules and advise removal --
function fh.DetectOldModules()
if fhGetAppVersion() > 6 then
local strPath = fhGetContextInfo("CI_APP_DATA_FOLDER").."\\Plugins\\"
local arrFile = { "compat53.lua"; "ltn12.lua"; "luasql\\sqlite3.dll"; "md5.lua"; "pl\\init.lua"; "socket.lua"; "utf8.lua"; "zip.dll"; }
for _, strFile in ipairs (arrFile) do
if fh.FSO:FileExists(strPath..strFile) then
fhMessageBox("\n Detected some old FH V6 library modules. \n\nPlease remove them by running the plugin: \n\n 'Delete old FH6 Plugin Module Files' \n","MB_OK","MB_ICONEXCLAMATION")
break
end
end
end
end -- function DetectOldModules
if fhGetAppVersion() > 6 then unpack = table.unpack end
-- Invoke FH Shell Execute API --
function fh.DoExecute(strExecutable,...)
-- strExecutable ~ full path of executable
-- ... ~ parameter list and optional error reporting function
-- return value ~ true if successful else false
local arg = {...}
local errFunction = fhMessageBox
if type(arg[#arg]) == 'function' then
errFunction = arg[#arg]
table.remove(arg)
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)
-- datDate ~ date point
-- return value ~ day number
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,"MB_OK","MB_ICONEXCLAMATION")
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
-- return value ~ integer year 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
-- ptrIndi ~ pointer to individual
-- return value ~ pointer to fact
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
-- return values ~ EARLIEST, MID, LATEST 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)
-- ptrInd ~ pointer to individual
-- intGens ~ generations to include
-- return values ~ EARLIEST, MID, LATEST dates
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)
-- ptrInd ~ pointer to individual
-- intGens ~ generations to include
-- return values ~ EARLIEST, MID, LATEST dates
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+tablex_v3
@Author: Mike Tate
@Version: 3.1
@LastUpdated: 08 Jan 2022
@Description: A Table Load Save Module.
@V3.1: Cater for full UTF-8 filenames.
@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,_,stransi,wasansi -- V1.2 -- V3.1 -- Added _,stransi,wasansi --!
-- 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
stransi,wasansi = general.FileNameToANSI(filename) -- V3.1 -- Cater for non-ANSI filename --!
file,err = io.open( stransi, "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()
if not ( wasansi ) then -- V3.1 -- Cater for non-ANSI filename --!
general.MoveFile(stransi,filename)
end
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
local stransi,wasansi = general.FileNameToANSI(sfile) -- V3.1 -- Cater for non-ANSI filename --!
if not ( wasansi ) then
general.CopyFile(sfile,stransi)
end
tables,err = loadfile( stransi )
if not ( wasansi ) then
general.DeleteFile(stransi) -- V3.1 -- Cater for non-ANSI filename --!
end
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+encoder_v3
@Author: Mike Tate
@Version: 3.6
@LastUpdated: 27 Aug 2024
@Description: Text encoder module for HTML XHTML XML URI UTF8 UTF16 ISO CP1252/ANSI character codings.
@V3.6: In fh.FileLines(...) cater for empty file;
@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 = "<br />" -- Markup language break tag default
local br_Lua = "<br ?/?>" -- 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 "<br>" or "<br >" or "<br/>" or "<br />" 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 <br> & <br > & <br/> & <br /> 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 <br> & <br > & <br/> & <br /> 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 <br> & <br > & <br/> & <br /> 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 <br> & <br > & <br/> & <br /> 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","MB_OK","MB_ICONEXCLAMATION")
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) or "" -- Read first lump from file and cater for empty 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" and #strText > 0 then -- Define ANSI conversion to current encoding and cater for empty file
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 "<br>" or "<br >" or "<br/>" or "<br />"
br_New = "<br />"
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.1
@LastUpdated: 23 Jan 2026
@Description: Progress Bar library module.
@V3.1: Use NATIVEPARENT amd CENTERPARENT.
@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.CENTERPARENT -- Show window default position is central -- V3.1
local intPosY = iup.CENTERPARENT
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
if fhGetAppVersion() > 6 then -- Window centres on FH parent -- V3.1
iup.SetAttribute(dlgGauge,"NATIVEPARENT",fhGetContextInfo("CI_PARENT_HWND"))
end
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: 4.6
@LastUpdated: 18 Feb 2026
@Description: Graphical User Interface Library Module
@V4.6: Improve handling of centred window RasterSize in fh.ShowDialogue(...);
@V4.5: Adjust CheckVersionInStore() for dedicated button use;
@V4.4: Introduce use of NATIVEPARENT and CENTERPARENT to centre on parent window by default; Ensure not off screen; Monitors with -ve X;
@V4.3: Added memo options to CheckVersionInStore;
@V4.2: Skip if standalone GEDCOM in fh.SaveSettings() and getDataFiles();
@V4.1: CheckVersionInStore() save & retrieve latest version in file; Remove old wiki Help features;
@V4.0: Cater for full UTF-8 filenames;
@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, 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
--]]
-- 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 intMinX = tblScrn[1]
local intMinY = tblScrn[2] -- V4.4
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")
iup.SetGlobal("UTF8MODE_FILE","NO") -- V4.0
function fh.SetUtf8Mode() -- Set IUP into UTF-8 mode
if iupVersion == "3.5" or stringx.encoding() == "ANSI" then return false end
iup.SetGlobal("UTF8MODE","YES")
iup.SetGlobal("UTF8MODE_FILE","YES") -- V4.0
return true
end -- function SetUtf8Mode
local function tblOfNames(...) -- Get table of dialogue Names including "Font","Main" by default -- V4.4
local arg = {...}
local tblNames = {"Font";"Main";}
for intName, strName in ipairs(arg) do
if type(strName) == "string"
and strName ~= "Font"
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.CENTERPARENT
tblName.CoordY = iup.CENTERPARENT
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(intMinX,intX,intMaxW-intWide) -- Ensure X co-ordinate positions window on screen -- V4.4
end
if intY and intY < iup.CENTERPARENT then
intY = intDimension(intMinY,intY,intMaxH-intHigh) -- Ensure Y co-ordinate positions window on screen -- V4.4
end
tblName.Raster = strRas or "x"
tblName.CoordX = tonumber(intX) or iup.CENTERPARENT -- V4.4
tblName.CoordY = tonumber(intY) or iup.CENTERPARENT -- V4.4
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","Main" by default
for intName, strName in ipairs(tblOfNames(...)) do
local tblName = tblNameFor(strName)
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 = {}
local strClip = general.StrLoadFromFile(strFileName) -- V4.0
for strLine in strClip:gmatch("[^\r\n]+") do -- V4.0
if #tblField == 0
and strLine:match("^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
else
for i,j in pairs (tblClipData) do
tblClipData[i] = nil -- Restore defaults and clear any junk -- V4.0
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.History = tostring(fh.LoadGlobal("History" ,fh.History))
fh.Balloon = tostring(fh.LoadGlobal("Balloon" ,fh.Balloon, "Machine"))
fh.LoadDialogue(...)
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
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","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 #strFileName > 0 then -- Skip for standalone GEDCOM -- V4.2
if type(table.save) == "function" then -- Save entire Settings File table per Project/User/Machine
table.save(tblClipData,strFileName)
else
local tblClip = {}
for strKey,strVal in pairs(tblClipData) do -- Else save Settings File lines with key & val fields -- V4.0
table.insert(tblClip,strKey.."="..strVal.."\n") -- V4.0
end
local strClip = table.concat(tblClip,"\n") -- V4.0
if not general.SaveStringToFile(strClip,strFileName) then
error("\nSettings file not saved successfully.\n\nMay need to Delete the following Plugin Data .dat file:\n\n"..strFileName.."\n\nError detected.")
end
end
end
break
end
end
end -- function SaveSettings
function fh.CheckWindowPosition(tblName) -- Ensure dialogue window coordinates are on Screen
local tblSize = stringx.splitnumbers(tblName.Raster or "600x400","x") -- Get window dimensions from the previous use of plugin -- V4.4
local intWinW = tblSize[1]
local intWinH = tblSize[2]
if tonumber(tblName.CoordX) == nil
or tonumber(tblName.CoordY) == nil
or tonumber(tblName.CoordX) < intMinX -- V4.4 -- V3.5
or tonumber(tblName.CoordY) < intMinY -- V4.4 -- V3.5
or tonumber(tblName.CoordX) + intWinW > intMaxW -- V4.4
or tonumber(tblName.CoordY) + intWinH > intMaxH then -- V4.4
tblName.CoordX = iup.CENTERPARENT -- V4.4
tblName.CoordY = iup.CENTERPARENT -- V4.4
end
end -- function CheckWindowPosition
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] or -1
local intPosY = tblPosn[2] or -1
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
if tblName.CoordX == iup.CENTERPARENT
or tblName.CoordY == iup.CENTERPARENT then -- When centred on parent refresh this window size -- V4.4 -- V4.6
fh.RefreshDialogue(strName)
else
iupDialog.MinSize = "x" -- Minimum size (default "x" becomes nil)
iupDialog.RasterSize = tblName.Raster or "x" -- Raster size (default "x" becomes nil)
end
iupDialog.MaxSize = intMaxW.."x"..intMaxH -- Maximum size is virtual screen size
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) if iup.MainLoopLevel() > 0 then fh.SetWindowCoord(tblName) end end -- V3.5 -- V4.4
iupDialog.resize_cb = iupDialog.resize_cb or function(self) if iup.MainLoopLevel() > 0 then tblName.Raster=self.RasterSize end end -- V3.5 -- V4.4 -- V4.6
fh.RefreshDialogue(strName) -- Refresh to set Natural Size as Minimum Size
if strName == "Main" then
if fhGetAppVersion() > 6 then -- Main window centres on FH parent -- V4.4
iup.SetAttribute(iupDialog,"NATIVEPARENT",fhGetContextInfo("CI_PARENT_HWND"))
end
else
local tblMain = tblNameFor("Main") -- Others popup centrally in Main -- V4.4
local iupMain = tblMain.Dialog
if iupMain then -- Centre based on size of windows -- V1.4
local arrName = stringx.splitnumbers(tblName.Raster or "0x0")
local arrMain = stringx.splitnumbers(tblMain.Raster or arrName[1].."x"..arrName[2])
tblName.CoordX = iupMain.X + math.floor( (arrMain[1] - arrName[1]) / 2 )
tblName.CoordY = iupMain.Y + math.floor( (arrMain[2] - arrName[2]) / 2 )
elseif fhGetAppVersion() > 6 then -- This window centres on FH parent -- V4.4
iup.SetAttribute(iupDialog,"NATIVEPARENT",fhGetContextInfo("CI_PARENT_HWND"))
end
end
if strFrame:match("map") then -- Only dialogue mapping is required
iupDialog:map()
tblName.Frame = strFrame:gsub("%s-%a-map%a*[%s%p]*","") -- Remove "map" from frame mode ready for subsequent call
return
end
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 not strFrame:match("dialog") -- Inhibit MainLoop if "dialog" mode -- V4.1
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.CENTERPARENT
tblName.CoordY = nil -- iup.CENTERPARENT
elseif tblName.CoordX ~= iup.CENTERPARENT -- V4.4
and tblName.CoordY ~= iup.CENTERPARENT then
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 ")
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 dialogue -- V4.4
fh.AssignAttributes(tblAttr) -- Assign parent dialogue attributes
fh.RefreshDialogue(strName) -- Refresh parent window size & position and bring infront of other 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
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, iupMemo end -- Return label & dialogue controls so message can be changed and dialogue destroyed
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 -- V4.5
local strVersion = "0"
if strPlugin then
local strFile = fh.MachinePath.."\\VersionInStore "..strPlugin..".dat"
general.DeleteFile(strFile)
local lblMemo, iupMemo = fh.MemoDialogue("Checking for updated version in the Family Historian 'Plugin Store'.","Keep Dialogue")
local strRequest ="http://www.family-historian.co.uk/lnk/checkpluginversion.php?name="..strPlugin
local isOK, strReturn = pcall(httpRequest,strRequest)
iupMemo:destroy()
if not isOK then -- Problem with Internet access
fhMessageBox(strReturn.."\n The Internet appears to be inaccessible. ","MB_OK","MB_ICONEXCLAMATION")
elseif strReturn ~= nil then
strVersion = strReturn:match("([%d%.]*),%d*") -- Version digits & dots then comma and Id digits
end
end
return strVersion or "0"
end -- function VersionInStore
local function intVersion(strVersion) -- Convert version string to comparable integer
local intVersion = 0
local arrNumbers = {}
strVersion:gsub("(%d+)", function(strDigits) table.insert(arrNumbers,strDigits) end) -- V4.1
for i=1,5 do
intVersion = intVersion * 100 + tonumber(arrNumbers[i] or 0)
end
return intVersion
end -- local function intVersion
function fh.CheckVersionInStore() -- Check if later Version available in Plugin Store -- V4.5
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 'Plugin Store'.")
else
fh.MemoDialogue("No later Version of this Plugin is available from the '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 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 strDataFile
end -- local function getPluginDataFileName
local function getDataFiles(strScope) -- Compose the Plugin Data file & path & root names
local strPluginName = fh.Plugin
local strPluginPlain = stringx.plain(strPluginName)
local strDataRoot = "" -- Plugin data file root name -- V4.2
local strDataPath = "" -- Plugin data folder path name
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 > 0 then -- Standalone GEDCOM path is ""
strDataPath = strDataFile:gsub("\\"..strPluginPlain.."%.[D,d][A,a][T,t]$","")
strDataRoot = strDataPath.."\\"..strPluginName
general.MakeFolder(strDataPath) -- V3.4
end
return strDataFile, strDataPath, strDataRoot
end -- local function getDataFiles
function fh.Initialise(strVersion,strPlugin) -- Initialise the GUI module with optional Version & Plugin name
local strAppData = 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 ""),"MB_OK","MB_ICONEXCLAMATION")
end
else
fhMessageBox("\nPlugin has not been saved!","MB_OK","MB_ICONEXCLAMATION")
end
end
fh.History = fh.Version -- Version History
fh.Plugin = strPlugin or fh.Plugin -- Plugin Name from argument or default from file
fh.DefaultDialogue() -- Default "Font","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 = fhGetContextInfo("CI_PROJECT_DATA_FOLDER") -- Paths used by Load/SaveFolder for relative folders -- V4.0
fh.PublicPath = fhGetContextInfo("CI_PROJECT_PUBLIC_FOLDER") -- Public data folder path name -- V4.0
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
local StrLocation -- Declare global as upvalue because it is set to nil
local IntChecked -- Declare global as upvalue because it may be set nil
local StrSync -- Declare global as upvalue because it is not declared
local DicFactSet -- Declare global as upvalue because it may be set nil
-- Translate text to current output language -- -- V5.1
function StrTranslate(strText)
-- strText ~ Text to translate
return g_trans_misc["~HTML:"..strText] or g_trans_misc[strText] or strText
end -- function StrTranslate
-- Global Data Constants Definition --
function PresetGlobalData()
g_trans_misc = { Output_Language = ""; } -- Create global Language Pack translation table -- V5.1
if fhGetAppVersion() > 6 then
local strPart = "Family Historian" -- V6.1
if fhGetAppVersion() > 7 then
strPart = "Family Historian 8" -- V6.1
end
local strLang = iup_gui.GetRegKey("HKCU\\SOFTWARE\\Calico Pie\\"..strPart.."\\2.0\\Preferences\\Output Language Id") or "" -- V6.1
if #strLang > 2 then
local strPack = fhGetContextInfo("CI_APP_DATA_FOLDER").."\\Lang\\"..strLang.."\\lang_trans_misc.fh_lua"
dofile(strPack)
g_trans_misc.Output_Language = " for Output Language "..strLang
else
g_trans_misc.Output_Language = " for English"
end
end
-- GUI Global Constants
iup_gui.Gap = "2"
iup_gui.Margin = "2x2"
iup_gui.Balloon = "NO" -- Needed for PlayOnLinux/Mac -- V4.7
iup_gui.SetUtf8Mode()
IntTabGeoLoc = 1 -- Main tab (& Preferences tab) position values -- 1 Aug 2013
IntTabCreate = 2
IntTabPrefer = 3
StrLocation = nil -- Location entry is unselected -- V4.9
StrReverse = "OFF" -- Reverse Display Order is OFF -- V4.9
StrClickHere = "Click here to choose an Entry"
StrRegBiasTitle = "Region Bias Code"
StrGeoCodeTitle = "Smart Geocoding"
StrFromTagTitle = "Locations From"
StrStorageTitle = "Database In"
StrLoggingTitle = "Geocoder Logging"
StrColoursTitle = "Marker Colour" -- V6.0
StrMarkersTitle = "Marker Animation"
StrPrivacyTitle = "Privacy Filter"
StrFactSetTitle = "Fact Filter" -- V3.7
StrMapZoomTitle = "Map Zoom"
IntGetZoom = 12
IfGotZoom = false -- Used by ReadMapPane() -- V4.3
IntFromPLAC = 1 -- Locations From values for IntFromTag
IntFromADDR = 2
IntFromBOTH = 3 -- Locations From values for BOTH variants:
IntFromP = 3 -- ~ Place only
IntFromA = 4 -- Address ~ only
IntFromAP = 5 -- Address ~ Place
IntFromA_P_ = 6 -- Address ~ [Place]
IntFrom_A_P = 7 -- [Address] ~ Place
IntFrom_A__P_ = 8 -- [Address] ~ [Place]
IntFromINDI = 4 -- Special From value for Tree GUI table
TblFromName = { "Place Fields only"; "Address Fields only"; " ~ Place only"; "Address ~ only"; "Address ~ Place"; "Address ~ [Place]"; "[Address] ~ Place"; "[Address] ~ [Place]"; }
TblFromTag = { "PLAC"; "ADDR"; "BOTH"; }
StrFromTag = TblFromTag[IntFromPLAC]
TblFromType = { PLAC="Facts Place"; ADDR="Facts Address"; BOTH="Facts Address ~ Place"; }
TblStoreTAG = { ""; "REPO"; "SOUR"; "SOUR"; "_PLAC"; }
IntStoreFILE = 1 -- Geocode Database values for IntStorage
IntStoreREPO = 2
IntStoreSOUR = 3
IntStoreBOTH = 4
IntStorePLAC = 5
IntStorage = IntStoreFILE -- FH6
IntArchive = IntStoreFILE -- FH6
IntMarkNothing = 1 -- Marker Style values for IntMarkers
IntMarkSynchro = 2
IntMarkCascade = 3
IntLivingFlag = 1 -- Privacy Flag values for IntPrivacy
IntPrivateFlag = 2
IntBothFlags = 3
IntSwitchedOff = 4
IsFromNew = false -- Signal new Locations From plot data
-- Plot & Map Status Translation Tables
StrPlotNoData = "No Data" -- Plot status text strings related to TblPlotStates
StrPlotDefined = "%-?[0-9]+[%.,]-[0-9]-Defined" -- Lat/Longitude numerical pattern as below but Defined/non-Tentative
StrPlotTentative = "%-?[0-9]+[%.,]-[0-9]-" -- Lat/Longitude numerical pattern ( optional "-" 1+ digits optional "." or "," 0+ digits ) -- Decimal symbols . or ,
StrPlotNoPlot = "No Plot"
StrPlotNotFound = "Not Found" -- FH Status Not Found -- V4.9
StrPlotBlocked = "Blocked" -- FH Status Block (No Auto-Geocode) -- V4.9
StrPlotInvalid = "Invalid Request"
StrPlotDenied = "Request Denied"
StrPlotQuotaX = "Quota Exceeded"
StrPlotUnknown = "Unknown Error" -- V3.8
StrPlotAborted = "Plot Aborted" -- Force plot loops to abort when a major error detected -- V4.2
IntPlotNoData = 1 -- Plot status table index values related to TblPlotStates & TblStatusColor
IntPlotDefined = 2
IntPlotTentative = 3
IntPlotNotFound = 4 -- Not Found -- V4.9
IntPlotBlocked = 5 -- Block (No Auto-Geocode) -- V4.9
IntPlotInvalid = 6
IntPlotDenied = 7
IntPlotQuota = 8
IntPlotUnknown = 9 -- V3.8 "Red"
IntPlotSumTotal = 10 -- V3.8 "Black"
StrMapNoMap = "No Map" -- Map status text strings related to TblMapStates
StrMapFilename = "map[0-9]+%.html" -- Map Folder filename pattern ( "map" 1+ digits ".html" ) -- %d V3.5
StrMapOmitted = "Omitted"
StrMapFiltered = "Filtered"
IntMapNoMap = 1 -- Map status table index values related to TblMapStates & TblStatusColor
IntMapFilename = 2
IntMapOmitted = 4
IntMapFiltered = 6 -- V4.9
IntMapSumTotal = 10 -- V3.8 "Black"
IntHighlight = 10 -- V3.8
TblPlotStates = { "^$" ; "^"..StrPlotDefined.."$"; "^"..StrPlotTentative.."$" ; "^"..StrPlotNotFound.."$" ; "^"..StrPlotBlocked.."$"; "^"..StrPlotInvalid.."$"; "^"..StrPlotDenied.."$"; "^"..StrPlotQuotaX.."$"; "^"..StrPlotUnknown.."$"; } -- V3.8 -- V4.9
TblMapStates = { "^$" ; "^"..StrMapFilename.."$"; "^$" ; "^"..StrMapOmitted.."$" ; "^$" ; "^"..StrMapFiltered.."$"; }
TblStatusColour = { "Gray" ; "Green" ; "Purple" ; "Amber" ; "Amber" ; "Red" ; "Red" ; "Red" ; "Red" ; "Black"; } -- V4.9
StrMasterWeb = StrMasterWeb:gsub("Map of Locations for",StrTranslate("Map of Locations for")) -- V5.1
-- Filename Global Constants
StrFileNameHTM = iup_gui.MachineRoot..".htm" -- Map Pane HTML file
StrFileNameACT = iup_gui.MachineRoot..".act" -- Map Pane Action file
StrFileNameLOC = iup_gui.ProjectRoot..".loc" -- Locations Geocode database file
StrFileNameCSV = iup_gui.ProjectRoot.." {GeoPlot}.csv" -- Locations plotting CSV log file
-- FH Web Page HTML code strings
StrEOL = "\r\n" -- End Of Line is \r\n in FH V7 but \n in FH V5/6 -- V5.1
if fhGetAppVersion() < 7 then StrEOL = "\n" end
StrDivContent = "<div class=\"fhcontent fhpage%u%a+\">"..StrEOL -- <div class="fhcontent fhpageFam"> or fhpageInd or fhpageDesc or fhpageIndNarr or fhpageRec -- V5.1
StrDivSeeAlso = "<div class=\"FhSeeAlso\">"..StrEOL -- <div class="FhSeeAlso">\r\n -- V5.1
StrOldSeeAlso = "[<!%- ]-<p>[^\r\n]-</p>[ %->]-" -- <!-- <p>See also</p> --> -- V5.1 -- Cater for 'Improve Website or CD DVD HTML' plugin Tree Diagrams tab 'See also' section
StrNewSeeAlso = "<p>"..StrTranslate("See also").."</p>" -- <p>See also</p> translated -- V5.1 -- May have <!-- comment --> tags and may be translated to a foreign language
StrBegSeeAlso = StrEOL.."<ul>"..StrEOL -- \r\n<ul>\r\n -- V5.1
StrEndSeeAlso = "</ul>"..StrEOL.."</div>"..StrEOL -- </ul>\r\n</div>\r\n -- V5.1
StrH1Heading = "<h1 class=\"FhHdg1\">" -- <h1 class="FhHdg1">
StrMapHeaders = "" -- Map file script between "</title>" and "</head>" to replace {fhheaders}
StrMapMenubar = "" -- Map file script between "<body>" and "<div class" to replace {fhmenubar}
DicGeoPlot = { "Disabled"; "Google"; "Geoapify"; Disabled=0; Google=1; Geoapify=2; } -- V5.3 -- V6.0
IntGeoPlot = DicGeoPlot.Disabled -- Geocoder for plotting -- V5.3
StrAPI_Key = "" -- Google API Key such as "BIzaSyDfg3XAWDnSmAcAasUKvnScySIPnMtVUjp" -- V4.2
StrGeo_Key = "" -- Geoapify API Key such as "c9c19e912a1349ddb87c18882a4495ef9" -- V5.3
DicMapMode = { "GoogleLegacy"; "LeafletMaps"; "GoogleAdvanced"; Unknown=0; GoogleLegacy=1; LeafletMaps=2; GoogleAdvanced=3; } -- V5.3 -- V6.0
IntWebMaps = DicMapMode.Unknown -- Web Page Maps from DicMapMode -- V6.0 -- V5.3
IntMapMode = DicMapMode.LeafletMaps -- Plugin Map Pane is LeafletMaps -- V6.0 -- V5.3 -- V5.2 -- V4.3
DicColours = { "Red"; "Blue"; "Green"; Red=1; Blue=2; Green=3; } -- V6.0
IntLegends = 3 -- Plugin Map Pane Legends 3=Medium as below -- V5.3
ArrMapPane = {
"width:140%;height:140%;zoom: 70%;"; -- 1 = Tiny
"width: 98%;height: 98%;zoom:100%;"; -- 2 = Small
"width: 70%;height: 70%;zoom:140%;"; -- 3 = Medium
"width: 50%;height: 50%;zoom:196%;"; -- 4 = Large
"width: 40%;height: 40%;zoom:245%;"; -- 5 = Huge
}
StrMapPath = "|" -- Map paths with their map files in new format -- V4.3
StrBaseMap = "" -- Base Layer Map chosen in Map Pane in Leaflet Mode -- V4.3
StrTypeMap = "road" -- Type Id of Map chosen in Map Pane in Bing Mode -- V5.3
TblPlotLog = {}
local arrLead = { "^GeocodeResponse%."; "^ResourceSets%.ResourceSet%."; "^root%."; }
for intGeoPlot=1, #DicGeoPlot do -- Initialise locations plotting CSV log file data -- V5.3
TblPlotLog[intGeoPlot] = {}
TblPlotLog[intGeoPlot].Head = {}
TblPlotLog[intGeoPlot].Lead = arrLead[intGeoPlot] -- Leading label prefix to be removed
end
end -- function PresetGlobalData
-- 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("List","600x400") -- Custom "List" dialogue default size and centralisation
iup_gui.DefaultDialogue("Bars","Memo","List") -- GUI window RasterSize and X & Y co-ordinates for "Main","Font","Bars","Memo","List" dialogues
IntTabPosn = 0 -- Initial tab is undefined -- 1 Aug 2013 was IntTabGeoLoc Initial tab is Geocode Location Plots
IsSplashOn = "API" -- Is new user API Help page shown -- V5.3 -- V4.9
StrWarnAns = "Yes" -- Quota Exceeded warning window shown
StrGeoCode = "ON" -- Smart Geocoding option is ON
IntRegBias = 235 -- Default Geocoder Region Bias TLD Code is UK
IntLocZoom = 12 -- Map Pane Zoom for Geocode Location Plots
IntFromTag = IntFromPLAC -- Locations From Tag is "PLAC"
StrFromTag = TblFromTag[IntFromTag] -- V3.5
StrLogging = "OFF" -- Logging plot XML fields to CSV is OFF -- V5.3
StrFolder = iup_gui.PublicPath -- Output folder path for Web page Maps -- V5.1 ANSI in FH V5 else UTF-8
IsFromADDR = "" -- Locations From ADDR for Address ~ Place sub-options -- Only for Adjust V3.1 below
IsFromPLAC = "" -- Locations From PLAC for Address ~ Place sub-options -- Only for Adjust V3.1 below
IntMapZoom = 12 -- Map Pane Zoom for Create Web Page Maps
IntMarkers = IntMarkCascade -- Markers Style is Cascade Drop
IntColours = DicColours.Red -- Map Marker Pin Colours 1=Red, 2=Blue, 3=Green -- V6.0
StrColours = DicColours[IntColours]
IntPrivacy = IntSwitchedOff -- Privacy Filter is Switched Off
StrFactSet = "OFF" -- Facts Filter option is OFF -- V3.7
StrBaseMap = "" -- Base Layer Map chosen in Map Pane in Leaflet Mode -- V4.3
end -- function ResetDefaultSettings
-- Load Sticky Settings from File --
function LoadSettings()
iup_gui.LoadSettings("Bars","Memo","List")
IntTabPosn = tonumber(iup_gui.LoadGlobal("TabPosn",IntTabPosn))
StrLocation = tostring(iup_gui.LoadGlobal("Entry" ,StrLocation))
StrWarnAns = tostring(iup_gui.LoadGlobal("WarnAns",StrWarnAns))
IsSplashOn = tostring(iup_gui.LoadGlobal("Splash" ,IsSplashOn,"Per Machine")) -- V4.9
IntGeoPlot = tonumber(iup_gui.LoadGlobal("GeoPlot",IntGeoPlot)) -- V5.3
StrAPI_Key = tostring(iup_gui.LoadGlobal("API_Key",StrAPI_Key,"Per Machine")) -- V4.2
StrGeo_Key = tostring(iup_gui.LoadGlobal("Geo_Key",StrGeo_Key,"Per Machine")) -- V5.3
if IntGeoPlot > DicGeoPlot.Geoapify
or IntGeoPlot == DicGeoPlot.Geoapify and #StrGeo_Key < 9
or IntGeoPlot == DicGeoPlot.Google and #StrAPI_Key < 9 then -- V6.0
IntGeoPlot = DicGeoPlot.Disabled
end
StrGeoCode = tostring(iup_gui.LoadGlobal("GeoCode",StrGeoCode))
StrReverse = tostring(iup_gui.LoadGlobal("Reverse",StrReverse)) -- V4.9
IntRegBias = tonumber(iup_gui.LoadGlobal("RegBias",IntRegBias))
IntStorage = tonumber(iup_gui.LoadGlobal("Storage",IntStorage))
IntArchive = tonumber(iup_gui.LoadGlobal("Archive",IntArchive)) -- FH6
IntLocZoom = tonumber(iup_gui.LoadGlobal("LocZoom",IntLocZoom))
IntFromTag = tonumber(iup_gui.LoadGlobal("FromTag",IntFromTag))
StrLogging = tostring(iup_gui.LoadGlobal("Logging",StrLogging)) -- V5.3
StrFolder = tostring(iup_gui.LoadFolder("Folder" ,StrFolder ))
IntWebMaps = tonumber(iup_gui.LoadGlobal("WebMaps",IntWebMaps)) -- V5.3
if IntWebMaps == DicMapMode.Unknown then -- First use -- V5.3 -- V6.0
IntWebMaps = DicMapMode.LeafletMaps -- Maps use Leaflet Mode
IntMapMode = DicMapMode.LeafletMaps
if StrLocation ~= "nil" then
fhMessageBox("\n Please see the 'Set Preference Options' tab new options :- \n\n The 'Location Plot Options' tab offers Google and Geoapify geocoding. \n\n The 'Web Page Map Options' tab offers Google and Leaflet map displays. \n")
end
end
if IntWebMaps ~= DicMapMode.LeafletMaps and #StrAPI_Key < 9 then
IntWebMaps = DicMapMode.LeafletMaps -- Maps need Leaflet Mode -- V6.0
end
IntMarkers = tonumber(iup_gui.LoadGlobal("Markers",IntMarkers))
IntColours = tonumber(iup_gui.LoadGlobal("Colours",IntColours)) -- V6.0
StrColours = DicColours[IntColours] -- V6.0
IntLegends = tonumber(iup_gui.LoadGlobal("Legends",IntLegends)) -- V5.3
StrMapPath = tostring(iup_gui.LoadGlobal("MapPath",StrMapPath)) -- V4.3
IsFromADDR = tostring(iup_gui.LoadGlobal("Is~ADDR",IsFromADDR)) -- Only for Adjust V3.1 below
IsFromPLAC = tostring(iup_gui.LoadGlobal("Is~PLAC",IsFromPLAC)) -- Only for Adjust V3.1 below
IntMapZoom = tonumber(iup_gui.LoadGlobal("MapZoom",IntMapZoom))
IntPrivacy = tonumber(iup_gui.LoadGlobal("Privacy",IntPrivacy))
StrFactSet = tostring(iup_gui.LoadGlobal("FactSet",StrFactSet)) -- V3.7
StrBaseMap = tostring(iup_gui.LoadGlobal("BaseMap",StrBaseMap)) -- V4.3
IntChecked = tonumber(iup_gui.LoadGlobal("Checked",IntChecked)) -- Only for Adjust V3.0 below
if IntFromTag == IntFromBOTH and IntChecked then -- Adjust V3.0 Address ~ Place to V3.2 Address ~ [Place] variant
IntFromTag = IntFromA_P_
end
local tblFromTag = { "--"; "-+"; "+-"; "++"; }
if tblFromTag[IntFromTag - 2] == IsFromADDR..IsFromPLAC then IntFromTag = IntFromTag + 2 end -- Adjust V3.1 to V3.2 Address ~ Place variants
local intFromTag = math.min(IntFromTag,IntFromBOTH) -- V3.5
local strFromTag = TblFromTag[intFromTag] -- V3.5
StrFromTag = strFromTag -- V3.5
SetIsFromFlags()
SaveSettings() -- Save sticky data settings
end -- function LoadSettings
-- Save Sticky Settings to File --
function SaveSettings()
iup_gui.SaveGlobal("MapPane",nil,"Machine") -- Remove old settings -- V5.3
iup_gui.SaveGlobal("IsGoogle",nil,"Machine")
iup_gui.SaveGlobal("BingAPI",nil,"Machine")
iup_gui.SaveGlobal("BingKey",nil,"Machine") -- V6.0
iup_gui.SaveGlobal("AzurKey",nil,"Machine") -- V6.0
iup_gui.SaveGlobal("TypeMap",nil,"Machine") -- V6.0
iup_gui.SaveGlobal("MapMode",nil,"Machine") -- V6.0
iup_gui.SaveGlobal("PlotNew",nil,"Machine")
iup_gui.SaveGlobal("TabPosn",IntTabPosn)
iup_gui.SaveGlobal("Entry" ,StrLocation)
iup_gui.SaveGlobal("WarnAns",StrWarnAns)
iup_gui.SaveGlobal("Splash" ,IsSplashOn,"Per Machine") -- V4.9
iup_gui.SaveGlobal("GeoPlot",IntGeoPlot) -- V5.3
iup_gui.SaveGlobal("API_Key",StrAPI_Key,"Per Machine") -- V4.2
iup_gui.SaveGlobal("Geo_Key",StrGeo_Key,"Per Machine") -- V5.3
iup_gui.SaveGlobal("GeoCode",StrGeoCode)
iup_gui.SaveGlobal("Reverse",StrReverse) -- V4.9
iup_gui.SaveGlobal("MapPath",StrMapPath) -- V4.3
iup_gui.SaveGlobal("RegBias",IntRegBias)
iup_gui.SaveGlobal("Storage",IntStorage)
iup_gui.SaveGlobal("Archive",IntArchive) -- FH6
iup_gui.SaveGlobal("LocZoom",IntLocZoom)
iup_gui.SaveGlobal("FromTag",IntFromTag)
iup_gui.SaveGlobal("Logging",StrLogging) -- V5.3
iup_gui.SaveFolder("Folder" ,StrFolder )
iup_gui.SaveGlobal("WebMaps",IntWebMaps) -- V5.3
iup_gui.SaveGlobal("Markers",IntMarkers)
iup_gui.SaveGlobal("Colours",IntColours) -- V6.0
iup_gui.SaveGlobal("Legends",IntLegends) -- V5.3
iup_gui.SaveGlobal("MapZoom",IntMapZoom)
iup_gui.SaveGlobal("Privacy",IntPrivacy)
iup_gui.SaveGlobal("FactSet",StrFactSet) -- V3.7
iup_gui.SaveGlobal("BaseMap",StrBaseMap) -- V4.3
iup_gui.SaveSettings("Bars","Memo","List") -- Includes "Main","Font","Bars","Memo","List" dialogues and "FontSet" & "History"
end -- function SaveSettings
-- Check if Google Maps API Key is Missing -- -- V5.3
function IsGoogleKeyMissing()
return ( #StrAPI_Key ~= 39 )
end -- function IsGoogleKeyMissing
-- Check if Google Maps used and no API Key -- -- V6.0
function IsGoogleMapAndNoKey(intGeoPlot,intWebMaps)
return ( IsGoogleKeyMissing() and ( intGeoPlot == DicGeoPlot.Google or intWebMaps ~= DicMapMode.LeafletMaps ) )
end -- function IsGoogleMapAndNoKey
-- Check if Geoapify API Key is Missing -- -- V5.3
function IsGeo_KeyMissing()
return ( #StrGeo_Key ~= 32 )
end -- function IsGeo_KeyMissing
-- Check if Geoapify used and no API Key -- -- V6.0
function IsGeoapifyAndNoKey(intGeoPlot)
return ( IsGeo_KeyMissing() and intGeoPlot == DicGeoPlot.Geoapify )
end -- function IsGeoapifyAndNoKey
-- Show Status Colour & Message Text --
function ShowStatusMessage(strColour,strMessage)
if IsGoogleMapAndNoKey(IntGeoPlot,IntWebMaps) -- V6.0
or IsGeoapifyAndNoKey(IntGeoPlot) then -- V6.0
strMessage = "Geocoder API Key Required see Help && Advice F.A.Q."
strColour = iup_gui.Risk
end
LblMessage.FgColor = strColour
LblMessage.Title = strMessage
iup.Flush()
end -- function ShowStatusMessage
function ShowDelErr(strError)
ShowStatusMessage(iup_gui.Risk,strError)
end -- ShowDelErr
-- Set IsFromADDR & IsFromPLAC Flags --
function SetIsFromFlags()
IsFromADDR = true -- Locations From ADDR for Address ~ Place sub-options
IsFromPLAC = true -- Locations From PLAC for Address ~ Place sub-options
if IntFromTag < IntFromBOTH then
return false
end
if IntFromTag == IntFromAP or IntFromTag == IntFromA_P_ then IsFromADDR = nil end
if IntFromTag == IntFromAP or IntFromTag == IntFrom_A_P then IsFromPLAC = nil end
return true
end -- function SetIsFromFlags
-- Have the Latitude & Longitude been Geocoded? --
function IsNotGeocoded(tblLocData)
if tblLocData then
if tblLocData.lat == "" or tblLocData.lng == "" then
return true
end
end
return false
end -- function IsNotGeocoded
-- Have the Latitude & Longitude got numbers? --
function IsPlotted(tblLocData)
if tblLocData then
if tonumber(tblLocData.lat) and tonumber(tblLocData.lng) then
return true
end
end
return false
end -- function IsPlotted
-- Encode Map Marker style into a table --
function TblMarkerStyle()
local strAnimate = "DROP"
local strTimeout = "300"
if IntMarkers == IntMarkNothing then strAnimate = "null" end
if IntMarkers == IntMarkCascade then strTimeout = "2000" end
return { Timeout=strTimeout; Animate=strAnimate; }
end -- function TblMarkerStyle
-- Ensure old map files use new format -- -- V4.3
function FixOldMapFiles(strFolder)
local strAns
if not StrMapPath:matches(strFolder) then
local arrFolder = general.GetFolderContents(strFolder)
for _, dicFile in ipairs (arrFolder) do -- V5.1
local strFile = dicFile.path
if strFile:match("^map%d+%.html$") then -- Found map file
local strText, intRepl = general.StrLoadFromFile(strFile):replace("\n <link type='text/css' rel='stylesheet' href='http://code.google.com/apis/maps/documentation/javascript/examples/default.css'>","") -- V4.4
if intRepl > 0 then -- Old format Google css link found & removed, so perform other updates?
strAns = strAns or fhMessageBox("\n Update other map files in this folder to new format? \n","MB_YESNO","MB_ICONQUESTION")
if strAns == "No" then return end
strText = strText:replace(" = new google.maps.LatLng("," = NewLatLong(")
if not strText:matches(" LocName[0] = ") then
strText = strText:gsub("( Caption%[(%d+)] = '(.-)';\n)","%1 LocName[%2] = '%3';\n")
end
general.SaveStringToFile(strText,strFile) -- Save updated map file
end
end
end
StrMapPath = StrMapPath..strFolder.."|" -- Add folder to list of map paths using new format
end
end -- function FixOldMapFiles
-- Create the mapfacts.js & mapfacts.css & mapfacts.png files -- -- V6.0
function MakeSharedFiles(strFolder,intMapMode)
local tblMarkerStyle = TblMarkerStyle()
local strMasterJS = StrMasterJS
local strAPI_Key = "Missing"
local strMapMode = DicMapMode[intMapMode or IntWebMaps] -- V5.3
local strColours = StrColours:lower() -- V6.0 -- Marker pin colours
if strMapMode:match("Google") then strAPI_Key = StrAPI_Key end -- V4.3
strMasterJS = strMasterJS:replace("{mapmode}",StrMasterMap[strMapMode],1) -- V4.3 -- V5.1 -- V5.3
strMasterJS = strMasterJS:replace("{api_key}",strAPI_Key,1) -- V4.2 -- V4.3 -- V5.1
strMasterJS = strMasterJS:replace("{map_id#}","DEMO_MAP_ID",1) -- V6.0 -- For Google Maps Advanced Marker Elements Map_Id
strMasterJS = strMasterJS:replace("{colours}",strColours) -- V6.0 -- Marker pin colours
strMasterJS = strMasterJS:replace("{animate}",tblMarkerStyle.Animate,1) -- V5.1
strMasterJS = strMasterJS:replace("{timeout}",tblMarkerStyle.Timeout,1) -- V5.1
strMasterJS = strMasterJS:replace("{setzoom}",IntMapZoom,1) -- V5.1
strMasterJS = strMasterJS:replace("{basemap}",StrBaseMap,1) -- V4.3 -- V5.1 Leaflet Base Layer
strMasterJS = strMasterJS:replace("Latitude" ,StrTranslate("Latitude"),1) -- V5.1
strMasterJS = strMasterJS:replace("Longitude",StrTranslate("Longitude"),1)
general.SaveStringToFile(strMasterJS,strFolder.."\\mapfacts.js")
local strMasterCSS = StrMasterCSS
general.SaveStringToFile(strMasterCSS,strFolder.."\\mapfacts.css")
if not strMapMode:match("Advanced") then -- V6.0 -- Create mapfacts.png file from leaflet markers URL
local strMasterURL = "http://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-"..strColours..".png" -- See https://github.com/pointhi/leaflet-color-markers
local strMasterPNG = strFolder.."\\mapfacts.png"
local strAnsi, wasAnsi = general.FileNameToANSI(strMasterPNG)
local fileHandle = general.OpenFile(strAnsi,"wb")
luacom.config.last_error = ""
WinHttp:Open("GET",strMasterURL,false)
WinHttp:Send()
if WinHttp.Status == 200 then
fileHandle:write(WinHttp.ResponseBody)
assert(fileHandle:close())
if not wasAnsi then
general.MoveFile(strAnsi,strMasterPNG)
end
else
fhMessageBox("\nError ~ Failed to Create Map Marker Icon mapfacts.png from:\n\n"..strMasterURL.."\n\nReason: "..luacom.config.last_error.."\n\nStatus: "..(WinHttp.Status or "nil").."\n\n","MB_OK","MB_ICONEXCLAMATION")
end
end
end -- function MakeSharedFiles
-- Open the Map Pane HTML Application --
function OpenMapPane()
general.DeleteFile(StrFileNameHTM,ShowDelErr)
general.DeleteFile(StrFileNameACT,ShowDelErr)
MakeSharedFiles(iup_gui.MachinePath,IntMapMode) -- Use chosen Plugin Map Pane mode -- V5.3
if not general.FlgFileExists(StrFileNameHTM) then -- Create Map Pane HTML file
local tblMarkerStyle = TblMarkerStyle()
local strScript = StrMasterHTM
strScript = strScript:replace("{setzoom}",IntLocZoom,1) -- V5.1
strScript = strScript:replace("{animate}",tblMarkerStyle.Animate,1) -- V5.1
strScript = strScript:replace("{basemap}",StrBaseMap,1) -- V4.3 -- V5.1 Leaflet Base Layer
strScript = strScript:replace("{mappane}",ArrMapPane[IntLegends],1) -- V5.3
strScript = strScript:replace("{plugin}",iup_gui.Plugin) -- Three replacements
general.SaveStringToFile(strScript,StrFileNameHTM)
end
OLEcontrol.com:Navigate(StrFileNameHTM)
ShowStatusMessage(iup_gui.Safe,"Map Pane Files Opened")
--[=[
OLEcontrol.com:GoHome() -- GoSearch() GoHome() work -- GoBack() GoForward() Refresh() do not
OLEcontrol.com:GoSearch()
--]=]
end -- function OpenMapPane
-- Run Map Action and wait for Synchronisation --
function RunMapAction(strA,str1,str2,str3,str4,str5) -- V4.2
if general.FlgFileExists(StrFileNameACT) then return end -- Ignore new request before old one has completed -- V6.0 Removed API Key checks as only Leaflet maps
dialogMain.Active = "NO"
local intCheck = 60 -- V6.0 was 60
local strStatus = ""
local strSync = ""
if StrSync == "+" then StrSync = "*" else StrSync = "+" end -- Alternate synchronisation characters
general.SaveStringToFile("<ACT>\n"..StrSync.."\n"..tostring(strA).."\n"..tostring(str1).."\n"..tostring(str2).."\n"..tostring(str3).."\n"..tostring(str4).."\n"..tostring(str5).."\n</ACT>\n",StrFileNameACT) -- V4.2
repeat
fhSleep(50,40) -- Check for Map Pane synchronisation, or excessive checks -- V6.0 was (50,40)
intCheck = intCheck - 1
strStatus = tostring( OLEcontrol.com.Document:getElementById("status").innerText )
if strStatus then -- Retrieve synch character status from HTML Document -- V6.1
strSync = strStatus:sub(1,1)
end
until strSync == StrSync or intCheck < 0
if intCheck < 0 then
ShowStatusMessage(iup_gui.Risk,"Map Action Sync Failed") -- V4.3
if WinHttp then WinHttpCopy = WinHttp end
WinHttp = nil -- Inhibit GeocodeLocation() API function on fault
else
if not WinHttp then WinHttp = WinHttpCopy end
end
--? ShowStatusMessage(iup_gui.Blue,strStatus.innerText.." intCheck="..tostring(intCheck)) -- Diagnostic
general.DeleteFile(StrFileNameACT,ShowDelErr) -- Remove old Map Pane Action file
dialogMain.Active = "YES"
end -- function RunMapAction
-- Hide Characters & < > in Map pane script -- -- V4.2
local arrHideChars = { ["&"]="\01as"; ["<"]="\01lt"; [">"]="\01gt"; }
function HideChars(strCaption)
return ( strCaption:gsub("[&<>]",arrHideChars) )
end -- function HideChars
-- Mark Lat/Longitude, LocName & Caption in Map pane -- -- V4.2
function MarkMapPane(strLatitude,strLongitude,strLocName,strCaption,strMode)
RunMapAction("MARK",tonumber(strLatitude),tonumber(strLongitude),strLocName,HideChars(strCaption or strLocName),strMode)
end -- function MarkMapPane
-- Move Lat/Longitude of existing Marker in Map pane -- -- V4.3
function MoveMapPane(strLatitude,strLongitude)
RunMapAction("MOVE",tonumber(strLatitude),tonumber(strLongitude))
end -- function MoveMapPane
-- Nullify all existing Markers from Map pane --
function NullMapPane()
RunMapAction("NULL")
end -- function NullMapPane
-- Plot Lat/Longitude, LocName, Caption & InfoWin data for Map pane unless offline -- -- V4.2 with LocName
function PlotMapPane(strLatitude,strLongitude,strLocName,strCaption,strInfoWin)
if WinHttp then RunMapAction("PLOT",tonumber(strLatitude),tonumber(strLongitude),strLocName,HideChars(strCaption or strLocName),strInfoWin) end
end -- function PlotMapPane
-- Drop multiple markers in chosen style in Map pane --
function DropMapPane(setBound)
local tblMarkerStyle = TblMarkerStyle()
RunMapAction("DROP",tblMarkerStyle.Animate,tblMarkerStyle.Timeout,setBound) -- V4.3 setBound
end -- function DropMapPane
-- Zoom to selected level in Map pane --
function ZoomMapPane(intZoomLevel)
RunMapAction("ZOOM",tostring(tonumber(intZoomLevel)))
end -- function ZoomMapPane
-- Read marker Location & map Zoom level from Map pane --
StrMapStatus = ""
function ReadMapPane() -- V4.3
local strStatus = OLEcontrol.com.Document:getElementById("status").innerText
if strStatus then
if StrMapStatus ~= strStatus then
StrMapStatus = strStatus
local strZoom = strStatus:match("Zoom=(%d+)")
if strZoom and #strZoom > 0 then -- and IntTabPosn == IntTabPrefer then
local intGetZoom = tonumber(strZoom) -- Get map zoom level after change -- V4.3
IntGetZoom = intGetZoom or IntGetZoom -- Ensure it is a number
IfGotZoom = true
end
local strLat, strLng, strAct = strStatus:match("Lat=(.+) Lng=(.+) Act=(%w+)")
if strLat and strLng and strAct and IntTabPosn == IntTabGeoLoc then
IntMapLat = tonumber(strLat) -- Get marker location for action -- V4.3
IntMapLng = tonumber(strLng)
StrMapAct = strAct -- Mid, End, New
end
local strBase = strStatus:match("Base=([%w ]+)")
if strBase then
StrBaseMap = strBase -- Get Leaflet Base Layer Map after change -- V4.3
end
local strType = strStatus:match("Type=([%w ]+)")
if strType then
local dicType = { r="road"; a="aerial"; be2="birdseye"; os="ordnanceSurvey"; }
StrTypeMap = dicType[strType] or "road" -- Get Bing Maps Type Id after change -- V5.3
end
end
end
end -- function ReadMapPane
-- Close the Map Pane HTML Application --
function CloseMapPane()
general.DeleteFile(StrFileNameACT,ShowDelErr)
general.DeleteFile(StrFileNameHTM,ShowDelErr)
general.DeleteFile(iup_gui.MachinePath.."\\mapfacts.js",ShowDelErr)
general.DeleteFile(iup_gui.MachinePath.."\\mapfacts.css",ShowDelErr)
general.DeleteFile(iup_gui.MachinePath.."\\mapfacts.png",ShowDelErr) -- V6.0
ShowStatusMessage(iup_gui.Safe,"Map Pane Files Closed")
fhSleep(100)
end -- function CloseMapPane
-- Get Text from XML Data using Node name --
function StrGetXmlText(strData,strNode)
return strData:match("<"..strNode..">(.-)</"..strNode..">") or ""
end -- function StrGetXmlText
-- Determine state Index of a Plot/Map Status value --
function IntStatusIndex(strStatus,tblStates)
local intIndex = IntPlotNoData
if strStatus then
for intState, strState in ipairs(tblStates) do
if string.find(strStatus,strState) then
intIndex = intState
break
end
end
end
return intIndex
end -- function IntStatusIndex
-- Refresh the Latitude or Longitude value --
function RefreshValue(txtControl,txtValue,strPlotMode)
txtValue = txtValue or ""
strPlotMode = strPlotMode or ""
local intIndex = IntStatusIndex(txtValue..strPlotMode,TblPlotStates)
if intIndex == IntPlotDefined
or intIndex == IntPlotTentative then -- Round value to 7 decimal places
txtValue = string.format("%4.7f",txtValue)
end
txtControl.Value = txtValue
txtControl.FgColor = iup_gui[TblStatusColour[intIndex]]
end -- function RefreshValue
WinHttp = luacom.CreateObject("winhttp.winhttprequest.5.1")
luacom.config.abort_on_error = false -- V5.4
luacom.config.abort_on_API_error = false -- V5.4
-- Update Latitude & Longitude Geocode data --
function setLatitudeLongitude(strLat,strLng,strLocName,strPlotMode) -- V4.3
local tblLocData = TblLocationData[strLocName]
if tblLocData then -- V6.0
if ( type(strLat) == "number" and type(strLng) == "number" )
or not tblLocData.plc
or not ( tonumber(tblLocData.lat) and tonumber(tblLocData.lng) ) then
tblLocData.lat = strLat -- Update if new numeric Lat/Long, not in Place, or not old numeric Lat/Long -- V3.9
tblLocData.lng = strLng -- i.e. Do not update unless new data is better than old data
SaveLocationsData(strLocName) -- e.g. 'Blue Springs, Hancock County, Ohio, USA' plots in FH Medina but not in Google Maps
if strLocName == StrLocation then
RefreshValue(TxtLatitude, tblLocData.lat,strPlotMode)
RefreshValue(TxtLongitude,tblLocData.lng,strPlotMode)
end
end
end
end -- function setLatitudeLongitude
-- Geocode a Location Address into Latitude & Longitude --
function StrGeocodeLocation(strLocName)
local strLog = strLocName
local strTxt = ""
local strAns = ""
local tblAns = { OK="Ok"; ["1"]="Ok"; ["0"]=StrPlotNotFound; ZERO_RESULTS=StrPlotNotFound; INVALID_REQUEST=StrPlotInvalid; REQUEST_DENIED=StrPlotDenied; OVER_QUERY_LIMIT=StrPlotQuotaX; UNKNOWN_ERROR=StrPlotUnknown; -- V3.8 -- V4.9
unknown=StrPlotNotFound; amenity="Ok"; building="Ok"; street="Ok"; suburb="Ok"; district="Ok"; postcode="Ok"; city="Ok"; county="Ok"; state="Ok"; country="Ok"; } -- V5.3 Geoapify
if TblLocationData[strLocName] and WinHttp then
local tblLoc = {}
local strLoc = strLocName
local strSub = TblLocationData[strLocName].sub or ""
if strSub ~= "" then strLoc = strSub end -- Replace Location with Standardized value if it exists
strLoc = strLoc or "" -- V5.0
strLoc = strLoc:gsub('%[%[.-%]%]','') -- Exclude double bracketed text from Geocoder as a user aid
strLoc = strLoc:gsub('".-"','') -- Exclude double quoted text from Geocoder as a user aid
strLoc = strLoc:gsub('%-',' ') -- Replace each hyphen with space to fix Stoke-upon-Trent -- V3.9
strLoc = strLoc:gsub(", -, -",", "):gsub(", -, -",", "):gsub("^[, ]+",""):gsub("[, ]+$","") -- Purge blank comma parts -- V5.0
strLog = strLoc
tblLoc = strLoc:split(",~"..string.char(7)) -- Split Location into fields separated by comma, tilde, or blob bell char
local strReg = TblRegBias[IntRegBias]:sub(1,2) -- Extract the Region Bias 2-letter code used in URL below
local strNat = TblNation[strReg] or "Other" -- Nation recognition to resolve smart code conflicts -- V5.0
tblLoc[0] = tblLoc[1] -- Cater for reverse Place order where Country is 1st -- V5.0
local intLoc = 0
repeat -- Tidy and convert tblLoc entry 0, then #tblLoc down to 1 -- V5.0
strLoc = tblLoc[intLoc] -- i.e. 1st part, then last part back to 1st part
strLoc = strLoc:gsub("^ *(.-) *$","%1") -- Strip leading and trailing spaces -- %s V3.5
if StrGeoCode == "ON" then -- If the Smart Geocoding option enabled, then translate any codes, etc
local strGeo = TblLocCode.Default[strLoc:upper()] or strLoc -- V5.0
strNat = TblNation[strGeo:lower()] or strNat -- Nation recognition to resolve Smart Code conflicts -- V5.0
strGeo = TblLocCode[strNat][strLoc:upper()] or strGeo
for _,tblGeo in ipairs(TblGeoCode) do -- Translations to improve Geocoder plotting
if string.match(strGeo.." ",tblGeo[1]) then
strGeo = tblGeo[2]
break
end
end
strLoc = strGeo:gsub("^UK$","United Kingdom") -- V5.3
end -- if StrGeoCode == "ON"
tblLoc[intLoc] = strLoc
if intLoc == 0 then intLoc = #tblLoc else intLoc = intLoc - 1 end
until intLoc < 1
strLoc = table.concat(tblLoc,", ") -- Rebuild location from tblLoc excluding tblLoc[0] -- V5.0
strLoc = encoder.StrEncode_URI(strLoc)
local strURL = ""
local strStat = "" -- Response XML keywords -- V5.3
local strLati = ""
local strLong = ""
local strConf = ""
local strType = ""
if IntGeoPlot == DicGeoPlot.Google then -- Google Maps geocoder URL & XML keywords -- V5.3
strURL = "https://maps.googleapis.com/maps/api/geocode/xml?address="..strLoc.."®ion="..strReg.."&key="..StrAPI_Key
strStat = "status"
strLati = "lat"
strLong = "lng"
strConf = "partial_match" -- "true" = Confidence partial match
strType = ""
elseif IntGeoPlot == DicGeoPlot.Geoapify then -- Geoapify geocoder URL & XML keywords -- V5.3
-- Convert pairs of UTF8 bytes to their %Hex codes
strLoc = strLoc:gsub("([\194-\244])([\128-\191])",function(strOne,strTwo) return "%"..string.format("%02x",strOne:byte()).."%"..string.format("%02x",strTwo:byte()) end)
strURL = "https://api.geoapify.com/v1/geocode/search?text="..strLoc.."&limit=1&bias=countrycode:"..strReg.."&format=xml&apiKey="..StrGeo_Key
strStat = "result_type"
strLati = "lat"
strLong = "lon"
strConf = "confidence"
strType = "result_type" -- better than "category"
else
return StrPlotUnknown -- Should never get here
end
local strLat = ""
local strLng = ""
local strGeo = ""
local strTyp = ""
local intSleep = 125 -- Interval between Geocoder calls
local intCheck = 2 -- Max repeats for not "Ok" status
repeat
luacom.config.last_error = "" -- V5.4
WinHttp:Open("GET",strURL,0) -- 0 was false
WinHttp:Send()
if WinHttp.Status == 200 then -- V5.4
strTxt = WinHttp.ResponseText -- Get status values as in tblAns above
else -- V5.4
local strAns = fhMessageBox("\nError ~ Geocoder Failed to Complete WinHttp Request:\n\n"..strURL.."\n\nReason: "..luacom.config.last_error.."\n\nStatus: "..(WinHttp.Status or "nil").."\n\nIf using a VPN then try pausing it and Geocode again.\n","MB_OKCANCEL","MB_ICONEXCLAMATION")
if strAns == "Cancel" then return StrPlotAborted end
strTxt = ""
end
strAns = StrGetXmlText(strTxt,strStat) -- V5.3
strAns = tblAns[strAns] or StrPlotUnknown -- V3.8
strLat = StrGetXmlText(strTxt,strLati) -- V5.3
strLng = StrGetXmlText(strTxt,strLong) -- V5.3
strGeo = StrGetXmlText(strTxt,strConf) -- V5.3
strTyp = StrGetXmlText(strTxt,strType) -- V5.3
if not (strLat and strLng) then
strAns = StrPlotNotFound -- V5.3
end
if strAns == "Ok" then
intCheck = 0 -- V3.8
else -- Try again after one second -- V3.8
setLatitudeLongitude(strAns,strAns,strLocName)
strGeo = "Geocoder API "..strAns.." see Help && Advice F.A.Q."
ShowStatusMessage(iup_gui.Warn,strGeo) -- V4.2
if strAns == StrPlotDenied
or strAns == StrPlotQuotaX
or strAns == StrPlotUnknown then -- V4.2
strAns = StrPlotAborted
elseif strAns == StrPlotNotFound then -- Remove leading location part for next try -- V5.0
strURL = strURL:gsub("address=.-%%2C%+","address=",1)
end
fhSleep(1000,100)
end
intCheck = intCheck - 1
until intCheck < 0 -- V3.8
if strAns == StrPlotAborted -- V4.2
or strAns == StrPlotNotFound then -- V5.3
setLatitudeLongitude("","",strLocName) -- Do NOT record abort errors in the Latitude & Longitude fields
ShowStatusMessage(iup_gui.Risk,strGeo)
else
setLatitudeLongitude( tonumber(strLat) or strAns, tonumber(strLng) or strAns, strLocName )
if strGeo == "true" then strGeo = "partial match" elseif strGeo == "" then strGeo = "good match" end -- V5.3
if tonumber(strGeo) then strGeo = tostring( math.floor( tonumber(strGeo) * 100 ) ).."%" end -- V5.3
strGeo = DicGeoPlot[IntGeoPlot+1].." "..strTyp.." Confidence: "..strGeo
ShowStatusMessage(iup_gui.Safe,"OK "..strGeo) -- V5.3
TblLocationData[strLocName].geo = strGeo -- V5.3+
end
fhSleep(intSleep,90)
end
if StrLogging == "ON" then -- If logging enabled then convert XML to Plot Log tables for CSV log file -- V5.3
local tblPlotLog = TblPlotLog[IntGeoPlot]
local strLeader = tblPlotLog.Lead
local intCol = 0
local strPre = ""
local dicRow = {}
strTxt = strTxt:gsub("^.-<","<")
strTxt = "<Original_Location>"..strLog.."</Original_Location>\n"..strTxt
for strLab, strVal in strTxt:gmatch("<(/?[%w_]+)>[\r\n ]*([^<]*)") do -- Match opening <label>value or closing </label> ignoring white space
if #strVal == 0 then
if strLab:match("^/") then -- Found closing </label> so try to remove from end of prefix
strPre = strPre:gsub(strLab:match("^/(.*)").."%.$","")
else -- Found opening <label> without value so add to end of prefix
strPre = strPre..strLab.."."
strPre = strPre:gsub(strLeader,"") -- Remove superfluous leading label prefix
end
elseif #strVal < 99 then -- Omit very long values such as Copyright text
strLab = strPre..strLab
while dicRow[strLab] do -- Compose unique CSV column header label
strLab = strLab.." "
end
strVal = strVal:gsub(",",";") -- Prevent commas upsetting CSV
dicRow[strLab] = strVal -- Save value against header label
if tblPlotLog.Head[strLab] then
intCol = tblPlotLog.Head[strLab] -- Header found before so get its column
else
intCol = intCol + 1
tblPlotLog.Head[strLab] = intCol -- Otherwise assign next column to header and shift rest along one column
table.insert(tblPlotLog.Head,intCol,strLab)
for intCol = intCol + 1, #tblPlotLog.Head do
tblPlotLog.Head[tblPlotLog.Head[intCol]] = intCol
end
end
end
end
table.insert(tblPlotLog,dicRow) -- Save row of CSV labelled values
end
return strAns
end -- function StrGeocodeLocation
-- Save Log File in CSV format -- -- V5.3
function SavePlotLogFileCSV()
for intGeoPlot=1, #DicGeoPlot do -- For each geocoder check if any rows saved
local tblPlotLog = TblPlotLog[intGeoPlot]
if #tblPlotLog > 0 then
local strCSV = ""
for _,strLab in ipairs (tblPlotLog.Head) do -- Compose the CSV header labels
strCSV = strCSV..strLab..","
end
strCSV = strCSV.."\n"
for _,dicRow in ipairs (tblPlotLog) do -- Compose the CSV row values
for _,strLab in ipairs (tblPlotLog.Head) do
strCSV = strCSV..(dicRow[strLab] or "")..","
end
strCSV = strCSV.."\n"
end
local strFileName = StrFileNameCSV:gsub("{GeoPlot}",DicGeoPlot[intGeoPlot+1])
general.SaveStringToFile(strCSV,strFileName) -- Save the CSV file to Plugin Data folder
end
end
end -- function SavePlotLogFileCSV
-- Create Locations Geocode Database --
function CreateLocationsData()
if TblLocationCode == nil then TblLocationCode = {} end
for _, strFromTag in ipairs(TblFromTag) do -- "PLAC" and "ADDR" and "BOTH"
if TblLocationCode[strFromTag] == nil then TblLocationCode[strFromTag] = {} end
if string.encoding() == "UTF-8" and TblLocationCode.Encoding ~= "UTF-8" then
local tblNew = {}
for strLoc, tblLoc in pairs (TblLocationCode[strFromTag]) do
local strNew = strLoc
if strFromTag == "BOTH" then strNew = strNew:gsub("…"," ~ ") end -- FH6
strNew = fhConvertANSItoUTF8(strNew)
tblNew[strNew] = tblLoc
end
TblLocationCode[strFromTag] = tblNew
end
for strLoc, tblLoc in pairs (TblLocationCode[strFromTag]) do
tblLoc.mod = tostring(tblLoc.mod):gsub("Manual","Defined") -- V4.9
tblLoc.lat = tostring(tblLoc.lat):gsub(StrPlotNoPlot,StrPlotNotFound) -- V4.9
tblLoc.lng = tostring(tblLoc.lng):gsub(StrPlotNoPlot,StrPlotNotFound) -- V4.9
tblLoc.plc = nil -- V3.5 needed to remove old Place Record names
end
end
TblLocationCode.Encoding = string.encoding()
end -- function CreateLocationsData
-- Load Locations Geocode Database File --
function LoadLocationsFile()
TblLocationCode = table.load(StrFileNameLOC)
CreateLocationsData()
table.save(TblLocationCode,StrFileNameLOC)
TblLocationData = TblLocationCode[StrFromTag]
end -- function LoadLocationsFile
-- Save Locations Geocode Database File --
function SaveLocationsFile()
table.save(TblLocationCode,StrFileNameLOC)
end -- function SaveLocationsFile
-- Load Place Record details into Locations database -- FH6
function LoadPlaceRecord(tblLocData,ptrRecord)
tblLocData.sub = fhGetItemText(ptrRecord,"~.STAN")
tblLocData.lat = fhGetItemText(ptrRecord,"~.LATLONG:LAT_NUMERIC")
tblLocData.lng = fhGetItemText(ptrRecord,"~.LATLONG:LONG_NUMERIC")
tblLocData.mod = ""
tblLocData.geo = " " -- Geocoder returned status -- V5.3+
local strStat = fhGetItemText(ptrRecord,"~.STAT") -- Status "" or "Tentative" with Lat/Long, or "Block(No Auto-Geocode)" or "Not Found" or "" without Lat/Long
if strStat == "No Auto-Geocode" then -- "mod" = "Defined" or "" with Lat/Long , or "lat"/"lng" = "Blocked" or "Not Found" or "" -- V4.9
strStat = StrPlotBlocked -- "Blocked" -- V4.9
end
if strStat == "" then
if tblLocData.lat..tblLocData.lng ~= "" then -- With Lat/Long -- V4.9
tblLocData.mod = "Defined"
end
elseif strStat ~= "Tentative" then -- "Blocked" or "Not Found" -- V4.9
tblLocData.lat = strStat
tblLocData.lng = strStat
end
end -- function LoadPlaceRecord
-- Load Locations Database from File or Gedcom --
function LoadLocationsData()
ShowStatusMessage(iup_gui.Warn,"Loading Location data, please wait...")
if IntStorage == IntStoreFILE
or IntStorage == IntStorePLAC then
LoadLocationsFile()
if IntStorage == IntStoreFILE then return end
end
CreateLocationsData()
local strStoreTag = TblStoreTAG[IntStorage] -- "REPO" or "SOUR" or "_PLAC"
local ptrRecord = fhNewItemPtr()
ptrRecord:MoveToFirstRecord(strStoreTag)
while ptrRecord:IsNotNull() do
for _, strFromTag in ipairs(TblFromTag) do -- "PLAC" and "ADDR" and "BOTH"
TblLocationData = TblLocationCode[strFromTag]
local strType = TblFromType[strFromTag]
if strFromTag == "BOTH" and string.encoding() == "UTF-8" then
strType = "Facts Address[ \226][~\128][ \166]Place" -- string.char(0xE2,0x80,0xA6) = "…"
end
local strHead = "Map Life "..strType.." Location"
local ptrType = fhGetItemPtr(ptrRecord,"~._TYPE") -- Source Type
local ptrRepo = fhGetItemPtr(ptrRecord,"~.REPO") -- Source to Repository link
local ptrNote = fhGetItemPtr(ptrRecord,"~.NOTE2") -- Source or Repository local Note
local ptrTarget = fhNewItemPtr()
local strLocName = fhGetDisplayText(ptrRecord)
if IntStorage == IntStoreBOTH and fhGetValueAsText(ptrType):match(strType) then
ptrRepo = fhGetValueAsLink(ptrRepo)
if ptrRepo:IsNotNull() then
ptrNote = fhGetItemPtr(ptrRepo,"~.NOTE2") -- Linked Repository local Note
end
end
local isHeadFound = false
if ptrNote:IsNotNull() then
if fhCallBuiltInFunction("GetParagraph", ptrNote, 1):match(strHead) then isHeadFound = true end
--? if fhGetLabelledText( ptrNote, strType) == "Location" then isHeadFound = true end -- alternative
end
if IntStorage == IntStoreREPO and isHeadFound then ptrTarget = ptrRecord end
if IntStorage == IntStoreSOUR and isHeadFound and fhGetValueAsText(ptrType):match(strType) then ptrTarget = ptrRecord end
if IntStorage == IntStoreBOTH and isHeadFound and fhGetValueAsText(ptrType):match(strType) then ptrTarget = ptrRepo end
if IntStorage == IntStorePLAC and strFromTag == "PLAC" then ptrTarget = ptrRecord end -- FH6 Place Record
if ptrTarget:IsNotNull() then
if strFromTag == "BOTH" and string.encoding() == "UTF-8" then
strLocName = strLocName:gsub("…"," ~ ") --FH6
local strType = TblFromType[strFromTag]
fhSetValueAsText(fhGetItemPtr(ptrRecord,"~.TITL"),strLocName)
fhSetValueAsText(ptrType,strType)
if IntStorage == IntStoreBOTH then
fhSetValueAsText(fhGetItemPtr(ptrRepo,"~.NAME"),strLocName)
end
fhSetValueAsText(ptrNote,(fhGetValueAsText(ptrNote):gsub("…"," ~ ",1)))
end
TblLocationData[strLocName] = {}
if IntStorage == IntStorePLAC then -- FH6 Place Record
TblLocationData[strLocName].plc = fhGetRecordId(ptrRecord)
LoadPlaceRecord(TblLocationData[strLocName],ptrTarget)
else
TblLocationData[strLocName].rec = fhGetRecordId(ptrRecord)
TblLocationData[strLocName].sub = (fhGetLabelledText( ptrNote, "Standard =")..fhGetLabelledText( ptrNote, "Substitute =")):gsub("^[ \t]+","") -- V4.9
TblLocationData[strLocName].lat = fhGetLabelledText( ptrNote, "Latitude =" ):gsub("^[ \t]+",""):gsub(StrPlotNoPlot,StrPlotNotFound) -- V4.9
TblLocationData[strLocName].lng = fhGetLabelledText( ptrNote, "Longitude =" ):gsub("^[ \t]+",""):gsub(StrPlotNoPlot,StrPlotNotFound) -- V4.9
TblLocationData[strLocName].mod = fhGetLabelledText( ptrNote, "Plot Mode =" ):gsub("^[ \t]+","")
TblLocationData[strLocName].geo = fhGetLabelledText( ptrNote, "Geocoding =" ):gsub("^[ \t]+","") -- V5.3+
end
end
TblLocationCode[strFromTag] = TblLocationData
end
--? SaveLocationsFile() -- diagnostic
ptrRecord:MoveNext("SAME_TAG")
end -- while not ptrRecord:IsNull()
TblLocationData = TblLocationCode[StrFromTag]
end -- function LoadLocationsData
-- Create new Location Data Entry --
function MakeNewLocationData(strLocName)
TblLocationData[strLocName] = { sub = ""; lat = ""; lng = ""; mod = ""; geo = ""; } -- V5.3+
end -- function MakeNewLocationData
-- Create new Repository Record --
function MakeNewRepositoryRecord(strLocName)
local ptrRecord = fhCreateItem("REPO")
fhSetValueAsText(fhCreateItem("NAME",ptrRecord),strLocName)
return ptrRecord, fhGetRecordId(ptrRecord)
end -- function MakeNewRepositoryRecord
-- Create new Source Record --
function MakeNewSourceRecord(strLocName)
local ptrRecord = fhCreateItem("SOUR")
fhSetValueAsText(fhCreateItem("TITL",ptrRecord),strLocName)
fhSetValueAsText(fhCreateItem("_TYPE",ptrRecord),TblFromType[StrFromTag])
return ptrRecord, fhGetRecordId(ptrRecord)
end -- function MakeNewSourceRecord
-- Save Locations Database entry to File or Gedcom --
function SaveLocationsData(strLocName)
if IntStorage == IntStoreFILE then
TblLocationData[strLocName].rec = nil
return
end
local strType, strHead, strData, ptrNote, ptrRepo -- V3.5
local ptrRecord = fhNewItemPtr()
local intRecord = TblLocationData[strLocName].rec or 0
if IntStorage == IntStorePLAC then -- FH6 -- V3.5
intRecord = TblLocationData[strLocName].plc or 0
else
strType = TblFromType[StrFromTag]
strHead = "Map Life "..strType.." Location\n"
strData = "Standard = \t"..(TblLocationData[strLocName].sub or "")..
"\nLatitude = \t"..(TblLocationData[strLocName].lat or "")..
"\nLongitude = \t"..(TblLocationData[strLocName].lng or "")..
"\nPlot Mode = \t"..(TblLocationData[strLocName].mod or "").."\n"
end
if intRecord > 0 then
-- Repository or Source or Place record exists
local strStoreTag = TblStoreTAG[IntStorage] -- "REPO" or "SOUR" or "_PLAC"
ptrRecord:MoveToRecordById(strStoreTag,intRecord)
if IntStorage ~= IntStorePLAC then
ptrNote = fhGetItemPtr(ptrRecord,"~.NOTE2") -- Source or Repository local Note
ptrRepo = fhGetItemPtr(ptrRecord,"~.REPO") -- Source to Repository link
--? if IntStorage == IntStoreBOTH and fhGetValueAsText(fhGetItemPtr(ptrRecord,"~._TYPE")) == strType then -- also check Source Type ?
if IntStorage == IntStoreBOTH then
ptrRepo = fhGetValueAsLink(ptrRepo)
if ptrRepo:IsNotNull() then
ptrNote = fhGetItemPtr(ptrRepo,"~.NOTE2") -- Linked Repository local Note
end
end
end
else
-- Process all Place records else create new Repository or Source Record
if IntStorage == IntStorePLAC then -- FH6
if StrFromTag ~= "PLAC" then return end -- FH6
local ptrRec = fhNewItemPtr()
ptrRec:MoveToFirstRecord("_PLAC")
while ptrRec:IsNotNull() do
local strLoc = fhGetItemText(ptrRec,"~.TEXT")
if not TblLocationData[strLoc] then -- Cater for Place record without any Place field
MakeNewLocationData(strLoc)
end
TblLocationData[strLoc].plc = fhGetRecordId(ptrRec)
if strLoc == strLocName then ptrRecord = ptrRec:Clone() end
if IsNotGeocoded(TblLocationData[strLoc]) then
LoadPlaceRecord(TblLocationData[strLoc],ptrRec)
end
ptrRec:MoveNext()
end
else
if IntStorage == IntStoreREPO then
ptrRecord, intRecord = MakeNewRepositoryRecord(strLocName)
else
ptrRecord, intRecord = MakeNewSourceRecord(strLocName)
if IntStorage == IntStoreBOTH then -- link Source to a new Repository
ptrRepo = MakeNewRepositoryRecord(strLocName)
fhSetValueAsLink(fhCreateItem("REPO",ptrRecord),ptrRepo)
ptrRecord = ptrRepo
end
end
ptrNote = fhCreateItem("NOTE2",ptrRecord) -- Source or Repository local Note
TblLocationData[strLocName].rec = intRecord
end
end
if IntStorage == IntStorePLAC then -- FH6 Place Record
local isOK
local strSub = tostring(TblLocationData[strLocName].sub)
local strLat = tostring(TblLocationData[strLocName].lat)
local strLng = tostring(TblLocationData[strLocName].lng)
local strMod = tostring(TblLocationData[strLocName].mod)
local ptrStat = fhGetItemPtr(ptrRecord,"~.STAT")
local strStat = fhGetValueAsText(ptrStat)
local strMode = strStat
local ptrStan = fhGetItemPtr(ptrRecord,"~.STAN")
local strStan = fhGetValueAsText(ptrStan)
if strSub == "" and strStan ~= "" then
if ptrStan:IsNotNull() then
isOK = fhDeleteItem(ptrStan)
end
elseif strSub ~= strStan then
if ptrStan:IsNull() then
ptrStan = fhCreateItem("STAN",ptrRecord)
end
isOK = fhSetValueAsText(ptrStan,strSub)
end
if strLat == StrPlotBlocked and strLng == StrPlotBlocked then
strMode = "No Auto-Geocode" -- Block(No Auto-Geocode) -- V4.9
strLat = ""
strLng = ""
TblLocationData[strLocName].mod = "" -- Ensure 'No Plot' status is updated -- V3.9
elseif strLat == StrPlotNotFound and strLng == StrPlotNotFound then
strMode = "Not Found" -- Not Found -- V4.9
strLat = ""
strLng = ""
TblLocationData[strLocName].mod = "" -- Ensure 'No Plot' status is updated -- V3.9
elseif strLat == "" and strLng == "" then
strMode = "" -- No Data -- V4.9
TblLocationData[strLocName].mod = "" -- Ensure 'No Plot' status is updated -- V3.9
end
local ptrLatLong = fhGetItemPtr(ptrRecord,"~.LATLONG")
local strLatLong = fhGetItemText(ptrRecord,"~.LATLONG:LAT_NUMERIC")..", "..fhGetItemText(ptrRecord,"~.LATLONG:LONG_NUMERIC")
local strLatLng = strLat:gsub("0+$","")..", "..strLng:gsub("0+$","") -- Remove trailing zeros -- V4.9
if strLatLng == ", " and strLatLong ~= ", " then -- Change to no Lat/Long plot -- V4.9
if ptrLatLong:IsNotNull() then
isOK = fhDeleteItem(ptrLatLong)
end
-- if strMode ~= "No Auto-Geocode" then
-- # strMode = "Not Found"
-- end
elseif strLatLng ~= strLatLong then
if tonumber(strLat) and tonumber(strLng) then -- V4.9
if ptrLatLong:IsNull() then
ptrLatLong = fhCreateItem("LATLONG",ptrRecord)
end
isOK = fhSetValueAsText(ptrLatLong,strLatLng)
if strMod == "Defined" then strMode = ""
elseif strMode ~= "Tentative" then
strMode = ""
TblLocationData[strLocName].mod = "Defined"
end
end
end
if strMode == "" and strStat ~= "" then -- Status: "" or "Tentative" with Lat/Long, or "No Auto-Geocode" or "Not Found" without Lat/Long
isOK = fhDeleteItem(ptrStat)
elseif strMode ~= strStat then
if ptrStat:IsNull() then
ptrStat = fhCreateItem("STAT",ptrRecord)
end
isOK = fhSetValueAsText(ptrStat,strMode)
end
else
fhSetValueAsText(ptrNote,strHead..strData) -- Save Location Data in local Note
end
--? ShowStatusMessage(iup_gui.Blue,"Locations Database Entry Saved") -- diagnostic
end -- function SaveLocationsData
-- Delete Locations Database Record in Gedcom --
function DeleteLocationRecord(intRecord)
local strType = TblFromType[StrFromTag]
local ptrRecord = fhNewItemPtr()
ptrRecord:MoveToRecordById(TblStoreTAG[IntStorage],intRecord or 0)
if ptrRecord:IsNotNull() then
--? if IntStorage == IntStoreBOTH and fhGetValueAsText(ptrType) == strType then -- also check Source Type ?
if IntStorage == IntStoreBOTH then
local ptrRepo = fhGetItemPtr(ptrRecord,"~.REPO")
ptrRepo = fhGetValueAsLink(ptrRepo)
if ptrRepo:IsNotNull() then
fhDeleteItem(ptrRepo) -- Delete Repository record linked to Source
end
end
fhDeleteItem(ptrRecord) -- Delete primary Source or Repository record
--? ShowStatusMessage(iup_gui.Blue,"Locations Database Entry Deleted") -- diagnostic
end
end -- function DeleteLocationRecord
-- Convert Locations Database entries from/to File or Records --
function ConvertLocationsData(intOldStorage,intNewStorage)
local ptrSourRecord = fhNewItemPtr()
local ptrRepoRecord = fhNewItemPtr()
local intOldRecordId = 0
local intNewRecordId = 0
-- Move local Note from one record to another
local function doMoveNotes(ptrGetRecord,ptrSetRecord)
local ptrGetNote = fhGetItemPtr(ptrGetRecord,"~.NOTE2")
local ptrSetNote = fhGetItemPtr(ptrSetRecord,"~.NOTE2")
if ptrSetNote:IsNull() then
ptrSetNote = fhCreateItem("NOTE2",ptrSetRecord)
end
fhSetValueAsText(ptrSetNote,fhGetValueAsText(ptrGetNote))
if intNewStorage == IntStoreBOTH then
fhDeleteItem(ptrGetNote) -- Delete original local Note
else
fhDeleteItem(ptrGetRecord) -- Delete original Record
end
end -- local function doMoveNotes
if intOldStorage == IntStorePLAC then -- V3.5
intOldStorage = IntArchive -- If converting from Place Records, reinstate archive storage media
if intOldStorage == intNewStorage then return end
IntStorage = intNewStorage -- V4.7
LoadLocationsData()
end
if intNewStorage == IntStorePLAC then -- V3.5
local strLocName, tblLocData = next(TblLocationCode.PLAC)
if tblLocData then tblLocData.plc = 0 end -- V3.6
end
for _, strFromTag in ipairs(TblFromTag) do -- "PLAC" and "ADDR" and "BOTH"
StrFromTag = strFromTag
TblLocationData = TblLocationCode[StrFromTag]
for strLocName, tblLocData in pairs(TblLocationData) do
intOldRecordId = tblLocData.rec
if intOldStorage == IntStoreFILE then -- Convert from Data File to any Gedcom Records
IntStorage = intNewStorage
SaveLocationsData(strLocName)
elseif intNewStorage == IntStoreFILE then -- Convert from Gedcom Records to Data File
IntStorage = intOldStorage
if intOldRecordId then -- FH6
DeleteLocationRecord(intOldRecordId)
end
tblLocData.rec = nil
elseif intNewStorage == IntStorePLAC then -- Convert from Gedcom Records to Place Records -- FH6
if strFromTag == "PLAC" then
IntStorage = intNewStorage
SaveLocationsData(strLocName)
end
--[=[
elseif intNewStorage == IntStoreREPO
or intNewStorage == IntStoreSOUR
or intNewStorage == IntStoreBOTH then -- Convert to Gedcom Records -- V4.9 ????
IntStorage = intNewStorage
SaveLocationsData(strLocName)
--]=]
elseif intOldStorage == IntStoreREPO then -- Convert Repository to Source / Source & Repository
ptrSourRecord, intNewRecordId = MakeNewSourceRecord(strLocName)
tblLocData.rec = intNewRecordId
if intOldRecordId then -- FH6
ptrRepoRecord:MoveToRecordById("REPO",intOldRecordId)
else
ptrRepoRecord, intOldRecordId = MakeNewRepositoryRecord(strLocName)
end
if intNewStorage == IntStoreSOUR then
doMoveNotes(ptrRepoRecord,ptrSourRecord)
elseif intNewStorage == IntStoreBOTH then
fhSetValueAsLink(fhCreateItem("REPO",ptrSourRecord),ptrRepoRecord)
end
elseif intOldStorage == IntStoreSOUR then -- Convert Source to Repository / Source & Repository
ptrRepoRecord, intNewRecordId = MakeNewRepositoryRecord(strLocName)
if intOldRecordId then -- FH6
ptrSourRecord:MoveToRecordById("SOUR",intOldRecordId)
else
ptrSourRecord, intOldRecordId = MakeNewSourceRecord(strLocName)
tblLocData.rec = intOldRecordId
end
doMoveNotes(ptrSourRecord,ptrRepoRecord)
if intNewStorage == IntStoreREPO then
tblLocData.rec = intNewRecordId
elseif intNewStorage == IntStoreBOTH then
fhSetValueAsLink(fhCreateItem("REPO",ptrSourRecord),ptrRepoRecord)
end
elseif intOldStorage == IntStoreBOTH then -- Convert Source & Repository to Repository / Source
if intOldRecordId then -- FH6
ptrSourRecord:MoveToRecordById("SOUR",intOldRecordId)
ptrRepoRecord = fhGetValueAsLink(fhGetItemPtr(ptrSourRecord,"~.REPO"))
else
ptrSourRecord, intOldRecordId = MakeNewSourceRecord(strLocName)
tblLocData.rec = intOldRecordId
ptrRepoRecord, intOldRecordId = MakeNewRepositoryRecord(strLocName)
fhSetValueAsLink(fhCreateItem("REPO",ptrSourRecord),ptrRepoRecord)
end
if intNewStorage == IntStoreREPO then
tblLocData.rec = fhGetRecordId(ptrRepoRecord)
fhDeleteItem(ptrSourRecord)
elseif intNewStorage == IntStoreSOUR then
doMoveNotes(ptrRepoRecord,ptrSourRecord)
end
end
iup.Flush()
end
TblLocationCode[StrFromTag] = TblLocationData
--? SaveLocationsFile() -- diagnostic
fhUpdateDisplay()
end
if intNewStorage == IntStoreFILE then SaveLocationsFile() end
--? if intOldStorage == IntStoreFILE then general.DeleteFile(StrFileNameLOC,ShowDelErr) end -- Delete or keep Locations file ?
StrFromTag = TblFromTag[math.min(IntFromTag,IntFromBOTH)]
TblLocationData = TblLocationCode[StrFromTag]
fhUpdateDisplay()
ShowStatusMessage(iup_gui.Safe,"Locations Database Converted")
end -- function ConvertLocationsData
local strControls = "[%z\01-\06\08-\32\127]+"
if fhGetAppVersion() > 6 then -- Cater for Lua 5.1 %z or Lua 5.3 \0 -- V4.7
strControls = "[\00-\06\08-\32\127]+"
end
-- Replace control chars with space but CRLF with blob in Location name --
function StrControls2SpaceNew(strLocName)
-- Replace \n with blob is essential for TblLocationData dictionary keys & Records
-- Replace all control chars with a space works better in GUI, Records, etc
if IntFromTag ~= IntFromPLAC then -- Except Place Fields need spaces to match Place Records and cannot include \n \r \t etc -- V3.5
strLocName = (strLocName or ""):gsub("\n",string.char(7)) -- Replace newline chars with bell char blobs
strLocName = strLocName:gsub(strControls," ") -- Replace any other multiple control & layout chars with one space -- %c%s V3.5 -- V4.7
end
return strLocName
end -- function StrControls2SpaceNew
-- Determine if special data tag of Husband/Wife for Family Record Id, or Residence or LDS Ordinations for Location Place/Address --
function IsDataTag(ptrFact)
local strGetTag = fhGetTag(ptrFact)
for _, strDataTag in ipairs( { "HUSB"; "WIFE"; "RESI"; "BAPL"; "CONL"; "ENDL"; "SLGC"; "SLGS"; } ) do
if strGetTag == strDataTag then return true end
end
return false
end -- function IsDataTag
-- Tidy multi commas & white space to single comma & space, remove any leading & trailing comma & space -- V4.2
function StrTidyCommas(strText)
strText = strText:gsub("[\t-\r ]*,[\t-\r ]*",",") -- Remove white space before & after each comma -- V4.2
strText = strText:gsub(",+",", ") -- Reduce multiple commas to one comma & space -- V4.2
strText = strText:gsub("^[, ]*(.-)[, ]*$","%1") -- Remove any leading or trailing comma & space -- V4.2
return strText
end -- function StrTidyCommas
-- Get Display Text & Remove Multiple/Leading/Trailing "," Place/Address Column Separators
-- NEW in V3.3 but prevents commas & spaces matching with FH6 _PLAC Records so needs adjusting
-- Used only by StrMakeLocation() and doAddFactData() in StrCreateWebPageMap() i.e. next two functions
function StrGetDisplayText(ptrFact,strTag,strMode)
strTag = strTag or ""
strMode = strMode or "STD"
local strText = fhGetDisplayText(ptrFact,strTag,strMode)
if strMode == "STD" or strTag:match("ADDR") then -- V3.3 FH6
strText = StrTidyCommas(strText) -- V4.2
if strMode == "STD" then
strText = strText:gsub(" from, "," from ") -- Remove comma after "from" -- V4.2
strText = strText:gsub(" from$","") -- Remove redundant "from" -- V4.2
strText = strText:gsub(" to, "," to ") -- Remove comma after "to" -- V4.2
strText = strText:gsub(" to$","") -- Remove redundant "to" -- V4.2
strText = strText:gsub(" in, "," in ") -- Remove comma after "in"
strText = strText:gsub(" in$","") -- Remove redundant "in"
end
end
return strText
end -- function StrGetDisplayText
-- Make Location Name and Plot/Map Data from Fact ADDR & PLAC data --
-- Used only by doBuildLocationsList() and doAddFactData() in StrCreateWebPageMap()
function StrMakeLocation(ptrFact)
local strFromTag = TblFromTag[math.min(IntFromTag,IntFromBOTH)] -- "PLAC" or "ADDR" or "BOTH"
local strLocName = ""
local strLocData = ""
local strADDR = ""
local strPLAC = ""
local str_Name = ""
local str_Data = ""
local str_PLAC = fhGetDisplayText(ptrFact,"~._PLAC","MIN") --_ To/From _PLAC for Emmigration & Immigration 19 June 2013
if IntFromTag < IntFromBOTH then -- "PLAC" or "ADDR" only
strLocName = StrGetDisplayText(ptrFact,"~."..strFromTag,"MIN") -- "~.PLAC" or "~.ADDR" tag -- Changed in V3.3
strADDR = strLocName
if strFromTag == "PLAC" then str_Name = str_PLAC end --_
else
strADDR = StrGetDisplayText(ptrFact,"~.ADDR","MIN") -- "BOTH" Address ~ Place variant -- Changed in V3.3
strPLAC = StrGetDisplayText(ptrFact,"~.PLAC","MIN") -- Changed in V3.3
strLocData = strADDR.." ~ "..strPLAC -- Return full Address ~ Place variant for Plots
str_Data = " ~ "..str_PLAC
if IntFromTag == IntFromP then strADDR = "" end -- " ~ Place only" variant
if IntFromTag == IntFromA then strPLAC = "" end -- "Address ~ only" variant
if IntFromTag == IntFromA then str_PLAC = "" end --_ "Address ~ only" variant 19 June 2013
if ( IsFromADDR or strADDR ~= "" ) and ( IsFromPLAC or strPLAC ~= "" ) then
strLocName = strADDR.." ~ "..strPLAC
strLocName = strLocName:gsub("^ ~ $","")
end
if ( IsFromADDR ) and ( IsFromPLAC or str_PLAC ~= "" ) then
str_Name = " ~ "..str_PLAC --_ 19 June 2013 looks OK
str_Name = str_Name:gsub("^ ~ $","") --_ 19 June 2013
end
end
if IntTabPosn == IntTabCreate then
if strFromTag ~= "PLAC" then
strLocData = "<br />"..strADDR -- Return the Address to append for Maps -- V4.2 <br />
else
strLocData = ""
end
end
return strLocName, strLocData, str_Name, str_Data --_ 19 June 2013 looks OK
end -- function StrMakeLocation
local strControlChar = "[%z\001-\031\127]+"
if fhGetAppVersion() > 6 then -- Cater for Lua 5.1 %z or Lua 5.3 \0 -- V4.7
strControlChar = "[\000-\031\127]+"
end
-- Create Web Page Map for one Individual --
function StrCreateWebPageMap(ptrIndi)
local strMapFolder = StrFolder.."\\"
local intFactIndex = 0 -- Relative ordinal position of Fact
local tblFactsData = {} -- Date, Fact Data, Location & Index per Fact
local tblLocations = {} -- Count of Facts per Location
local tblMapMarker = {} -- Map Marker number per Location
local tblFamily = {} -- Record Id of Spouse Family Records
local tblSpouse = {} -- Spouse Name of Spouse Family Records
local strJavaLists = "" -- Map Marker Java Lists for Locations
local isGeocoderOk = true -- Is Google Geocoder API OK -- V4.2
-- Check if Dictionary of Facts is empty -- V5.1
local function isDicFactSetEmpty()
return next(DicFactSet or {}) == nil
end -- local function isDicFactSetEmpty
-- Find Fact Labels in HTML Web Pages for Family Group Sheet or Individual Summary Report -- V3.7 -- V5.1 -- Rewritten
local function doFindFactLabels()
if StrFactSet == "ON" and IsFamilyTreeWebsite() and isDicFactSetEmpty() then
local strFind = '<li class="fhcol1 fhseclabel"><p>(.-):</p></li>' -- FH V7 tabbed report fact label pattern
if fhGetAppVersion() < 7 then strFind = '<tr><td>([^<]-[^:.])</td>' end -- FH V5/6 tabbed report fact label pattern
DicFactSet = {} -- Dictionary of fact labels found in web pages
local arrFile = {} -- Array of fam/ind HTML files in folder
local intFact = 0 -- Count of fact labels found
local tblList = general.FSO:GetFolder(StrFolder)
local tblEnum = luacom.GetEnumerator(tblList.Files)
local tblAttr = tblEnum:Next() -- Search files in folder
while tblAttr do
local strFile = tblAttr.path
if strFile:match("\\[fi][an][md][0-9]+%.html$") then -- fam/ind HTML file found
if #arrFile % 500 == 0 then
ShowStatusMessage(iup_gui.Warn,"Facts Filter Search, please wait...")
end
table.insert(arrFile,strFile) -- Save fam/ind HTML files
elseif strFile:match("\\[j-z][^\\]+$") then break end -- Abort when subsequent file found
tblAttr = tblEnum:Next()
end
if #arrFile > 2000 then
progbar.Start("Facts Filter Search",#arrFile) -- Start progress bar
end
for intFile, strFile in ipairs (arrFile) do -- Search fam/ind HTML files found
local strText = general.StrLoadFromFile(strFile)
local strType = strText:match('<div class="fhcontent fhpage(%u%a+)">',500)
if strType == "Fam" or strType == "Ind" then
for strFact in strText:gmatch(strFind) do -- Find fact labels in Family Group Sheet or Individual Summary Report
if not DicFactSet[strFact] then
intFact = intFact + 1 -- Count and save new fact label
DicFactSet[strFact] = true
end
end
else
break -- Narrative, Outline, Record Detail reports are not supported
end
if intFile % 100 == 0 then
progbar.Message("Searching "..strFile:match("[fi][an][md]").." html pages found "..intFact.." Fact labels.")
progbar.Step(100)
if progbar.Stop() then break end
end
end
progbar.Close()
end
end -- local function doFindFactLabels
-- Add Fact Data to Table
local function doAddFactData(ptrFact,strFactData) -- Revised for EMIG/IMMIgration facts -- V4.2
if isDicFactSetEmpty()
or DicFactSet[StrTranslate(fhCallBuiltInFunction("FactLabel",ptrFact))] then -- V5.1 -- V3.7
strFactData = strFactData..StrGetDisplayText(ptrFact) -- Add description of Fact including Place -- V3.3 -- V4.2 -- V5.1
local isLocation = false
local strLocName,strAddress,str2ndPlace,strPlcName = StrMakeLocation(ptrFact)-- Get Location name, optional Address to append, and from/to Place, etc -- V4.2
if strLocName ~= "" then -- Ignore empty Location
strLocName = StrControls2SpaceNew(strLocName) -- Uses newline bell blob
tblLocations[strLocName] = (tblLocations[strLocName] or 0) + 1 -- Count how many Facts at this Location
strAddress = strAddress:gsub("^<br />$","") -- V4.2 <br />
isLocation = true
end
if str2ndPlace ~= "" then -- Ignore empty 2nd Place
if strPlcName == "" then strPlcName = str2ndPlace end
strPlcName = StrControls2SpaceNew(strPlcName) -- Uses newline bell blob
tblLocations[strPlcName] = (tblLocations[strPlcName] or 0) + 1 -- Count how many Facts at this Location
str2ndPlace = StrTidyCommas(str2ndPlace:gsub("^ ~ ",""))
strFactData, str2ndPlace = strFactData:match("(.+)( %l+ "..str2ndPlace:plain()..")$")
isLocation = true
end
if isLocation then -- Either Location or 2nd Place
local datDate = fhNewDate()
local ptrDate = fhGetItemPtr(ptrFact,"~.DATE") -- Obtain the Date in order to sort Facts -- V4.2
if ptrDate:IsNotNull() then
datDate = fhGetValueAsDate(ptrDate)
else
datDate:SetSimpleDate(fhCallBuiltInFunction("Today"))
end
intFactIndex = intFactIndex + 1 -- Save the Fact details
table.insert( tblFactsData, { Date=datDate:Clone(); FactData=strFactData; LocName=strLocName; Address=strAddress; Place=str2ndPlace; PlcName=strPlcName; Index=intFactIndex; } )
end
end
end -- local function doAddFactData
-- Determine if Privacy Flags allow output
local function flagsOK(ptrIndi)
if ( IntPrivacy == IntLivingFlag or IntPrivacy == IntBothFlags ) and fhGetDisplayText(ptrIndi,"~._FLGS.__LIVING","MIN") == "Y" then return false end -- or fhGetItemText(ptrIndi,"~._FLGS.__LIVING")
if ( IntPrivacy == IntPrivateFlag or IntPrivacy == IntBothFlags ) and fhGetDisplayText(ptrIndi,"~._FLGS.__PRIVATE","MIN") == "Y" then return false end -- or fhGetItemText(ptrIndi,"~._FLGS.__PRIVATE")
return true
end -- local function flagsOK
-- Process one Individual Record and associated Family Records
local function doProcessIndividual(ptrIndi)
-- Process Individual Facts
local ptrFact = fhNewItemPtr()
ptrFact:MoveToFirstChildItem(ptrIndi)
while ptrFact:IsNotNull() do
if fhIsFact(ptrFact) or IsDataTag(ptrFact) then
doAddFactData(ptrFact,"")
end
ptrFact:MoveNext("ANY")
end
-- Loop all Family Facts
local ptrFamily = fhNewItemPtr()
local ptrSpouse = fhNewItemPtr()
local ptrChild = fhNewItemPtr()
local intFamily = 1
local intSpouse = 1
local intChild = 1
-- Determine if found a new Spouse Family Record Id
local function isNewSpouse()
local strRecordId = fhGetRecordId(ptrFamily)
for _, strFamilyId in ipairs(tblFamily) do
if strFamilyId == strRecordId then return false end
end
return true
end -- local function isNewSpouse
ptrFamily:MoveTo(ptrIndi,"~.FAMS["..intFamily.."]>")
while ptrFamily:IsNotNull() do
-- Process Family Facts
ptrFact:MoveToFirstChildItem(ptrFamily)
while ptrFact:IsNotNull() do
if fhIsFact(ptrFact) or IsDataTag(ptrFact) then
ptrSpouse:MoveTo(ptrIndi,"~.~SPOU["..intFamily.."]>")
if flagsOK(ptrSpouse) then
local strSpouse = fhGetDisplayText(ptrIndi,"~.~SPOU["..intFamily.."]>NAME","MIN")
doAddFactData(ptrFact,"with "..strSpouse..":<br />") -- V4.2 <br />
if isNewSpouse() then
tblFamily[intSpouse] = fhGetRecordId(ptrFamily)
tblSpouse[intSpouse] = strSpouse
intSpouse = intSpouse + 1
end
end
end
ptrFact:MoveNext("ANY")
end
-- Process Child Births
intChild = 1
ptrChild:MoveTo(ptrFamily,"~.CHIL["..intChild.."]>")
while ptrChild:IsNotNull() do
if flagsOK(ptrChild) then
ptrFact:MoveTo(ptrChild,"~.BIRT")
if fhIsFact(ptrFact) then
doAddFactData(ptrFact,"Child: "..fhGetDisplayText(ptrChild,"~.NAME","MIN")..":<br />") -- V4.2 <br />
end
end
intChild = intChild + 1
ptrChild:MoveTo(ptrFamily,"~.CHIL["..intChild.."]>")
end
intFamily = intFamily + 1
ptrFamily:MoveTo(ptrIndi,"~.FAMS["..intFamily.."]>")
end
end -- local function doProcessIndividual
-- Build JavaScript Map Marker Location Lists for HTML
local function intJavaScriptLists()
local strLatitude = ""
local strLongitude = ""
local intLocTotal = 0
local arrJavaLists = {} -- V4.2
local intMapMarker = 0
tblMapMarker = {}
for strLocName, intLocCount in pairs(tblLocations) do
if not TblLocationData[strLocName] then -- v3.6
MakeNewLocationData(strLocName) -- v3.6
end
if IsNotGeocoded(TblLocationData[strLocName]) and IntGeoPlot ~= DicGeoPlot.Disabled then -- V5.3
local strAns = StrGeocodeLocation(strLocName) -- V4.2
SaveLocationsData(strLocName) -- v3.6
IsFromNew = true -- v3.6
iup.Flush()
if strAns == StrPlotAborted then -- V4.2
isGeocoderOk = false
return -1
end
end
end
for strLocName, intLocCount in pairs(tblLocations) do
tblMapMarker[strLocName] = ""
if TblLocationData[strLocName] then
if IsPlotted(TblLocationData[strLocName]) then
strLatitude = TblLocationData[strLocName].lat
strLongitude = TblLocationData[strLocName].lng
tblMapMarker[strLocName] = tostring(intMapMarker) -- Track the Map Marker number for each mapped Location
strLocName = strLocName:gsub(strControlChar," ") -- Make control characters spaces -- V4.7
strLocName = StrTidyCommas(strLocName) -- StrTidyCommas -- V4.2
strLocName = strLocName:gsub("\\","\\\\") -- Escape any \ characters -- V4.2
local strCaption = strLocName:gsub("'","\\'") -- Escape any ' characters -- V4.2
local strFactsText = StrTranslate("%d recorded Facts at") -- V5.1
if intLocCount == 1 then strFactsText = StrTranslate("%d recorded Fact at") end
strFactsText = strFactsText:replace("%d",tostring(intLocCount),1) -- V5.1
table.insert(arrJavaLists," LatLong["..intMapMarker.."] = NewLatLong("..strLatitude..", "..strLongitude..");")
table.insert(arrJavaLists," LocName["..intMapMarker.."] = '"..encoder.StrEncode_XML(strLocName).."';") -- V3.3 -- StrEncode_XML V3.5 -- V4.2
table.insert(arrJavaLists," Caption["..intMapMarker.."] = '"..strCaption.."';") -- V4.2
--? table.insert(arrJavaLists," InfoWin["..intMapMarker.."] = '"..intLocCount..strFactsText.." at';")
table.insert(arrJavaLists," InfoWin["..intMapMarker.."] = '"..strFactsText.."';")
intMapMarker = intMapMarker + 1
intLocTotal = intLocTotal + intLocCount
end
end
end
strJavaLists = table.concat(arrJavaLists,"\n") -- V4.2
return intLocTotal
end -- local function intJavaScriptLists
-- Comparison for table sort that returns true when 1st is less than 2nd
local function isLessThan(tbl1st,tbl2nd)
local intResult = tbl1st.Date:Compare(tbl2nd.Date)
if intResult == 0 then -- Dates equal so compare Index to retain original Fact order
return tbl1st.Index < tbl2nd.Index -- True if 1st Index < 2nd Index
else
return intResult == -1 -- True if 1st Date < 2nd Date
end
end -- local function isLessThan
-- Compose HTML Anchor Tag for Facts
local function getAnchor(strName,strFact) -- V4.2
local strMark = tblMapMarker[strName] or "" -- Marker from intJavaScriptLists() above
if #strMark > 0 and #strFact > 0 then
return "<a href='#' onclick='ZoomToMarker("..strMark..")'>"..encoder.StrEncode_XML(strFact).."</a>" -- StrEncode_XML V3.5
end
return encoder.StrEncode_XML(strFact) -- Include any Fact with Location but without Marker -- V4.3
end -- local function getAnchor
-- Build Facts List linked to Map Markers for HTML
local function strFactsList(tblFactLink) -- V4.2 revised
local arrFactList = {}
for _, strFactLink in ipairs(tblFactLink) do
table.insert(arrFactList," <li>"..strFactLink.."</li>")
end
table.sort(tblFactsData,isLessThan) -- Sort Facts Data table by Date and Index
for _, tblEntry in ipairs(tblFactsData) do
local strFactData = tblEntry.FactData or ""
local strLocName = tblEntry.LocName or ""
local strAddress = tblEntry.Address or ""
local str2ndPlace = tblEntry.Place or ""
local strPlcName = tblEntry.PlcName or ""
if #str2ndPlace == 0 then
if #strLocName > 0 then -- Simple fact with one Place/Address -- V4.2
strFactData = getAnchor(strLocName,strFactData..strAddress)
end
else
if #strLocName == 0 then -- EMIG/IMMIG with only second Place -- V4.2
strFactData = getAnchor(strPlcName,strFactData..str2ndPlace..strAddress)
else -- EMIG/IMMIG with both Places/Address -- V4.2
strFactData = getAnchor(strLocName,strFactData)..getAnchor(strPlcName,str2ndPlace)..getAnchor(strLocName,strAddress)
end
end
if #strFactData > 0 then
table.insert(arrFactList," <li>"..strFactData.."</li>") -- V4.2
end
end
return table.concat(arrFactList,"\n") -- V4.2
end -- local function strFactsList
-- Insert Link in Featured Web Page
local function doInsertLink(strWebFile,strMapFile,strIndName)
strWebFile = strMapFolder..strWebFile
local strWebPage = general.StrLoadFromFile(strWebFile)
local strLnkText = ("<li><a href='"..strMapFile.."'>.-"..strIndName.."</a></li>"..StrEOL):gsub("%%","%%%%") -- V5.1 -- Cater for translated "Map of Locations for"
if not strWebPage:match(strLnkText) then -- Insert link to map file, including "See also" section if necessary
strWebPage = strWebPage:gsub("("..StrDivContent..")("..StrH1Heading..")","%1"..StrDivSeeAlso..StrNewSeeAlso..StrBegSeeAlso..StrEndSeeAlso.."%2",1) -- V5.1
strLnkText = strLnkText:gsub("%.%-",StrTranslate("Map of Locations for").." ",1) -- V5.1 -- Translate "Map of Locations for" and add unique separator
strWebPage = strWebPage:gsub(StrDivSeeAlso..StrOldSeeAlso..StrBegSeeAlso,StrDivSeeAlso..StrNewSeeAlso..StrBegSeeAlso..strLnkText,1) -- V5.1 -- Cater for 'Improve Website or CD DVD HTML' plugin Tree Diagrams tab 'See also' section
general.SaveStringToFile(strWebPage,strWebFile)
end
end -- local function doInsertLink
-- Output HTML Web Page Map
local function strCreateWebPage(ptrIndi)
local strRecordId = fhGetRecordId(ptrIndi)
local strIndName = encoder.StrEncode_XML(fhGetDisplayText(ptrIndi)) -- StrANSI_XML() added in V3.3 on 06 June 2013 -- StrEncode_XML V3.5
local strMapFile = "map"..strRecordId..".html"
local arrFactLink = {}
local strFactsFor = "'>"..StrTranslate("Detailed Facts for").." " -- V5.1
local strWebPage = " "
if IsFamilyTreeWebsite() then
-- If a Family Tree CD/Website featured Individual web page exists link to/from it
local strIndFile = "ind"..strRecordId..".html"
if general.FlgFileExists(strMapFolder..strIndFile) then
table.insert(arrFactLink,"<a href='"..strIndFile..strFactsFor..strIndName.."</a>") -- V4.2
-- Also need to link from Individual page
doInsertLink(strIndFile,strMapFile,strIndName)
end
-- If any Family Tree CD/Website featured Family web pages exist link to/from them
for intFam, strRecordId in ipairs(tblFamily) do
local strFamFile = "fam"..strRecordId..".html"
if general.FlgFileExists(strMapFolder..strFamFile) then
local strSpouse = encoder.StrEncode_XML(tblSpouse[intFam]) -- StrEncode_XML V3.5
if strSpouse ~= "" then strSpouse = " and "..strSpouse end -- One or two parent family? Changed in V3.3 on 06 June 2013
table.insert(arrFactLink,"<a href='"..strFamFile..strFactsFor..strIndName..strSpouse.."</a>") -- V4.2
-- Also need to link from Family web page
doInsertLink(strFamFile,strMapFile,strIndName)
end
end
end
strWebPage = StrMasterWeb
strWebPage = strWebPage:replace("{fhheaders}",StrMapHeaders)
strWebPage = strWebPage:replace("{fhmenubar}",StrMapMenubar)
strWebPage = strWebPage:replace("{titlename}",strIndName) -- ,2)
strWebPage = strWebPage:replace("{javalists}",strJavaLists)
strWebPage = strWebPage:replace("{factslist}",strFactsList(arrFactLink))
strMapFile = strMapFolder..strMapFile
general.SaveStringToFile(strWebPage,strMapFile)
return strMapFile:replace(strMapFolder,"")
end -- local function strCreateWebPage()
-- Main body of StrCreateWebPageMap function starts here --
local strMapState = ""
local strIndi = fhGetItemText(ptrIndi,"~.NAME:SURNAME_FIRST") -- or fhGetDisplayText(ptrIndi)
intFactIndex = 0
tblFactsData = {}
tblLocations = {}
tblFamily = {}
doFindFactLabels()
if flagsOK(ptrIndi) then
doProcessIndividual(ptrIndi)
if intJavaScriptLists() > 0 then
strMapState = strCreateWebPage(ptrIndi)
else
strMapState = StrMapOmitted
end
else
strMapState = StrMapFiltered
end
return strMapState, isGeocoderOk -- V4.2
end -- function StrCreateWebPageMap
-- Does folder contain FH Family Tree CD/Website files --
function IsFamilyTreeWebsite()
if general.FlgFileExists(StrFolder.."\\fhstyle.css")
and general.FlgFileExists(StrFolder.."\\_nameindex.html") then
return true
else
return false
end
end -- function IsFamilyTreeWebsite()
-- Set default or FH Family Tree CD/Website headers & menubar --
function SetWebPageMapSections()
-- Set default headers & menubar HTML sections
local strHeaders = " <meta http-equiv='content-type' content='text/html; charset=utf-8'>"
local strMenubar = " "
if IsFamilyTreeWebsite() then
local tblNameIndex = {}
-- Folder contains FH Family Tree CD/Website so map file needs FH headers & FH menubar
local strNameIndex = general.StrLoadFromFile(StrFolder.."\\_nameindex.html")
for strLine in strNameIndex:gmatch("(.-)[\n\r]+") do
table.insert(tblNameIndex,strLine) -- Read HTML file up to first <div class=...> in body -- V5.1
if strLine:find("<div class=\"fhcontent fhpage_Index\">") then break end
end
local strNameIndex = table.concat(tblNameIndex,"\n")
-- Extract headers script from between "</title>" and "</head>"^$()%.[]*+-?)
strHeaders = strNameIndex:gsub(".-</title>\n*(.-)\n*</head>.*","%1",1)
-- Remove "<style type="text/css"> /*<![CDATA[*/ ... /*]]>*/ </style>" added by Improve Website or CD DVD HTML Plugin
strHeaders = strHeaders:gsub('\n*<style type="text/css"> /%*<!%[CDATA%[%*/ .- /%*%]%]>%*/ </style>',"",1)
-- Extract menubar script from between "<body>" and "<div class="
strMenubar = strNameIndex:gsub(".-<body.->(.-)\n*<div class=.*","%1",1)
end
StrMapHeaders = strHeaders
StrMapMenubar = strMenubar
end -- function SetWebPageMapSections
-- GUI Warning Prompt Dialogue --
function GUI_WarnDialogue(strHead,strText)
local intAnswer, strAnswer =
iup_gui.WarnDialogue(
{ strHead; iup_gui.Warn; },
{ strText; iup_gui.Info; "Description of operation to accept or reject"; },
{ "Yes" ; iup_gui.Safe; "Accept the operation"; },
{ "No" ; iup_gui.Risk; "Reject the operation"; }
)
return strAnswer
end -- function GUI_WarnDialogue
-- GUI Some Plots Prompt Dialogue --
function GUI_PlotDialogue(tblPlotCounts)
local intButton = 0
local isConfirm = true
local iupButtons = iup.vbox { Gap=iup_gui.Gap; Margin=iup_gui.Margin; Homogeneous="YES"; }
-- Create the GUI buttons
for intAttr, tblAttr in ipairs ( {
-- 1~Index ; 2~Title ; 3~Tip
{ IntPlotDefined ; " Plotted Locations " ; "Geocode the Plotted Locations" ; };
{ IntPlotTentative ; " Tentative Locations" ; "Geocode the Tentative Locations" ; };
{ IntPlotNotFound ; " No Plot Locations " ; "Geocode the No Plot Locations" ; };
{ IntPlotInvalid ; " Invalid Locations " ; "Geocode the Invalid Locations" ; };
{ IntPlotNoData ; " No Data Locations " ; "Geocode the No Data Locations" ; };
} ) do
local intIndex = tblAttr[1] -- Index to Plot Counts and Status Color tables
local intPlots = tblPlotCounts[intIndex]
local strTitle = "Geocode Plot the "..intPlots..tblAttr[2]
if intPlots == 1 then strTitle = strTitle:gsub("Locations","Location",1) end
local btnButton = iup.button{ Title=strTitle; Tip=tblAttr[3]; Expand="YES"; FgColor=iup_gui[TblStatusColour[intIndex]]; action=function() intButton=intIndex return iup.CLOSE end; }
if intPlots == 0 then
btnButton.Active="NO"
btnButton.Font=iup_gui.FontBody:gsub(", (.-) (%-?[0-9]+)$",", Strikeout %1 %2") -- V3.5
end
iup.Append(iupButtons,btnButton)
end
local tglConfirm = iup.toggle { Expand="YES"; RightButton="YES"; Value="ON"; Title="Confirm Every Location Geocode Plot?"; Tip="Choose whether each Location is plotted selectively or unconditionally"; action=function(self,intState) isConfirm=(intState==1) end; }
local btnDestroy = iup.button { Visible="NO"; Floating="IGNORE"; action=function() return iup.CLOSE end; } -- Hidden button for default Esc and Enter action
local strHead = "Geocode Plot Some Locations"
local strText = "Choose the class of Locations to Geocode Plot"
-- Create dialogue and turn off resize, maximize, minimize, and menubox except Close button
local dialogPlot = iup.dialog { Title=iup_gui.Plugin.." "..strHead;
iup.vbox { Alignment="ACENTER"; Gap=iup_gui.Gap; Margin=iup_gui.Margin;
iup.frame { Font=iup_gui.FontHead; FgColor=iup_gui.Info; Title=strHead;
iup.vbox { Font=iup_gui.FontBody; FgColor=iup_gui.Info; Gap=iup_gui.Gap; Margin=iup_gui.Margin;
iup.label { Title=strText..":"; Tip=strText; };
iupButtons; tglConfirm; btnDestroy;
};
};
};
close_cb = function() intButton=0 end;
}
iup_gui.ShowDialogue("Memo",dialogPlot,btnDestroy,"popup")
return intButton, isConfirm
end -- function GUI_PlotDialogue
-- GUI Skip/Plot Location Prompt Dialogue --
function GUI_SkipDialogue(strClassColor)
local isConfirm = true
local isGeocode = true
-- Create the GUI buttons
local btnPlotThis = iup.button { Title="Plot this Location"; Tip="Plot selected Location" ; }
local btnSkipThis = iup.button { Title="Skip this Location"; Tip="Skip selected Location" ; }
local btnPlotRest = iup.button { Title="Plot all the Rest" ; Tip="Plot rest of Locations" ; }
local btnSkipRest = iup.button { Title="Skip all the Rest" ; Tip="Skip rest of Locations" ; }
-- Set other GUI button attributes
for iupName, tblAttr in pairs ( {
-- Control = 1~isConfirm; 2~isGeocode
[btnPlotThis] = { true ; true };
[btnSkipThis] = { true ; false };
[btnPlotRest] = { false ; true };
[btnSkipRest] = { false ; false };
} ) do
iupName.Expand = "YES"
iupName.FgColor = iup_gui.Safe
iupName.action = function() isConfirm=tblAttr[1] isGeocode=tblAttr[2] return iup.CLOSE end
end
local strHead = "Geocode Plot Some Locations"
local strText = "Choose which Locations to Geocode Plot"
-- Create dialogue and turn off resize, maximize, minimize, and menubox except Close button
local dialogSkip = iup.dialog { Title=iup_gui.Plugin.." "..strHead;
iup.vbox { Alignment="ACENTER"; Gap=iup_gui.Gap; Margin=iup_gui.Margin;
iup.frame { Font=iup_gui.FontHead; FgColor=iup_gui.Info; Title=strHead;
iup.vbox { Font=iup_gui.FontBody; FgColor=strClassColor; Gap=iup_gui.Gap; Margin=iup_gui.Margin;
iup.label { Title=strText..":"; Tip=strText; };
iup.hbox { btnPlotThis; btnSkipThis; Homogeneous="YES"; };
iup.hbox { btnPlotRest; btnSkipRest; Homogeneous="YES"; };
};
};
};
close_cb = function() end;
}
iup_gui.ShowDialogue("Memo",dialogSkip,btnPlotThis,"popup")
return isConfirm, isGeocode
end -- function GUI_SkipDialogue
local lblBingKey = iup.label { Title=" Bing Maps Key: " ; Tip="Enter your Bing Maps personal API Key code"; } -- V5.3
local txtBingKey = iup.text { Value=" " ; Tip="Enter your Bing Maps personal API Key code"; } -- V5.3
-- GUI Main Dialogue --
function GUI_MainDialogue()
local function strDegree(strText)
if fhGetAppVersion() > 5 then strText = fhConvertANSItoUTF8(strText) end
return strText
end -- local function strDegree
-- Create the Geocode Location Plots controls with title/value and tooltip, etc
local btnPlotAll = iup.button{ Title="Geocode Plot All Locations" ; Tip="Geocode Plot all the Location entries"; }
local btnPlotSome = iup.button{ Title="Geocode Plot Some Locations"; Tip="Geocode Plot selected Location entries"; }
local btnPlotThis = iup.button{ Title="Geocode Plot This Location" ; Tip="Geocode Plot the displayed Location entry"; }
local lblPlotStats = iup.label { Title="Statistics:" ; Tip="Geocoder Plot Locations Database Statistics"; }
local lblPlotted = iup.label { Title=" " ; Tip="Number of Locations with fully Plotted Latitude/Longitude"; }
local lblTentative = iup.label { Title=" " ; Tip="Number of Locations with Tentative plot Latitude/Longitude"; }
local lblNoPlot = iup.label { Title=" " ; Tip="Number of Locations with Blocked status or Not Found by Geocoder"; }
local lblInvalid = iup.label { Title=" " ; Tip="Number of Locations with Invalid Request/Request Denied from Geocoder"; }
local lblNoData = iup.label { Title=" " ; Tip="Number of Locations with no Latitude/Longitude and not yet Geocoded"; }
local lblPlotTotal = iup.label { Title=" " ; Tip="Number of Locations listed in total"; }
local lblLocation = iup.label { Title="Location:" ; Tip="Select any Location to manage its Database entry"; }
local btnLocation = iup.button{ Title=" " ; Tip="Select any Location to manage its Database entry"; }
local lblStandard = iup.label { Title="Standardized:" ; Tip="Standardized modern or alternative Location for this Database entry"; }
local txtStandard = iup.text { Value=" " ; Tip="Standardized modern or alternative Location for this Database entry"; }
local lblLatitude = iup.label { Title="Latitude:" ; Tip=strDegree("Latitude for chosen Location Database entry \n from -90° S.Pole to +90° N.Pole") ; }
TxtLatitude = iup.text { Value=" " ; Tip=strDegree("Latitude for chosen Location Database entry \n from -90° S.Pole to +90° N.Pole") ; }
local lblLongitude = iup.label { Title="Longitude:" ; Tip=strDegree("Longitude for chosen Location Database entry \n from -180° West to +180° East"); }
TxtLongitude = iup.text { Value=" " ; Tip=strDegree("Longitude for chosen Location Database entry \n from -180° West to +180° East"); }
local btnLocToSub = iup.button{ Title=" Copy Location\n to Standardized"; Tip="Copy the Location text field to the Standardized text field of this entry"; Padding="0x5"; }
local btnClearAll = iup.button{ Title=" Clear This Entry" ; Tip="Clear the Standardized, Latitude, and Longitude values of this entry"; }
local btnClearSub = iup.button{ Title=" Clear Standardized" ; Tip="Clear the Standardized value of this entry"; }
local btnClearLatLng= iup.button{ Title=" Clear Lat/Longitude" ; Tip="Clear the Latitude & Longitude values of this entry"; } -- V4.9
local btnSetBlocked = iup.button{ Title=" Block Geocoding" ; Tip="Set the Latitude & Longitude values to Blocked(no auto-geocode) for this entry"; } -- V4.9
local btnNextPlot = iup.button{ Title="Next Plotted" ; Tip="Show the succeeding (Alt=preceding) \n Fully Plotted/Nudged Lat/Lng Location entry"; }
local btnNextDefined= iup.button{ Title="Next Tentative" ; Tip="Show the succeeding (Alt=preceding) \n Tentative plotted Lat/Lng Location entry"; }
local btnNextSubs = iup.button{ Title="Next Standardized" ; Tip="Show the succeeding (Alt=preceding) \n Standardized substitute Location entry"; }
local btnNextUnplot = iup.button{ Title="Next Unplotted" ; Tip="Show the succeeding (Alt=preceding) \n Unplotted Location entry (No Plot, Invalid, or No Data)"; }
local btnMoveNorth = iup.button{ Title="N" ; Tip=strDegree("Nudge the Latitude North dependent on Zoom \n (maximum +90° N.Pole)"); }
local btnMoveSouth = iup.button{ Title="S" ; Tip=strDegree("Nudge the Latitude South dependent on Zoom \n (minimum -90° S.Pole)"); }
local btnMoveWest = iup.button{ Title="W" ; Tip=strDegree("Nudge the Longitude West dependent on Zoom \n (minimum -180° West)"); }
local btnMoveEast = iup.button{ Title="E" ; Tip=strDegree("Nudge the Longitude East dependent on Zoom \n (maximum +180° East)"); }
-- Create the Create Web Page Maps controls with title/value and tooltip, etc
local btnMapAll = iup.button { Title="Map All Individuals" ; Tip="Create the Web Page Maps for all the Individuals"; }
local btnMapSome = iup.button { Title="Map Some Individuals" ; Tip="Create the Web Page Maps for selected Individuals"; }
local btnMapThis = iup.button { Title="Map This Individual" ; Tip="Create a Web Page Map for the displayed Individual"; }
local lblMapStats = iup.label { Title="Statistics:" ; Tip="Mapped Individuals Statistics"; }
local lblMapped = iup.label { Title=" " ; Tip="Number of Individuals with Web Page Maps"; }
local lblOmitted = iup.label { Title=" " ; Tip="Number of Individuals without any Facts with Plotted Locations"; }
local lblFiltered = iup.label { Title=" " ; Tip="Number of Individuals excluded by the Privacy Filter"; }
local lblNoMap = iup.label { Title=" " ; Tip="Number of Individuals without Web Page Maps"; }
local lblMapTotal = iup.label { Title=" " ; Tip="Number of Individuals listed in total"; }
local lblIndiName = iup.label { Title="Individual:" ; Tip="Select any Individual [Record Id] to manage the Web Page Map"; }
local btnIndiName = iup.button { Title=" " ; Tip="Select any Individual [Record Id] to manage the Web Page Map"; }
local lblMapState = iup.label { Title="Map:" ; Tip="Web Page Map filename or Omitted/Filtered status"; }
local txtMapState = iup.text { Value=" " ; ReadOnly="YES" ; Tip="Web Page Map filename or Omitted/Filtered status"; }
local btnDeleteMap = iup.button { Title="Delete This Map" ; Tip="Delete this Web Page Map file from the current Maps Folder"; }
local btnPrevUnmap = iup.button { Title="Prior Unmapped" ; Tip="Show the preceding Individual without a Web Page Map file"; }
local btnNextUnmap = iup.button { Title="Next Unmapped" ; Tip="Show the succeeding Individual without a Web Page Map file"; }
local btnPrevMapped= iup.button { Title="Prior Mapped" ; Tip="Show the preceding Individual with a Web Page Map file"; }
local btnNextMapped= iup.button { Title="Next Mapped" ; Tip="Show the succeeding Individual with a Web Page Map file"; }
-- Create the Set Preference Options controls with title/value and tooltip, etc
local lblAPI_Key = iup.label { Title="Google Maps Key: " ; Tip="Enter your Google Maps personal API Key code"; } -- V4.2
local txtAPI_Key = iup.text { Value=" " ; Tip="Enter your Google Maps personal API Key code"; } -- V4.2
local lblGeo_Key = iup.label { Title=" Geoapify Key: " ; Tip="Enter your Geoapify personal API Key code"; } -- V5.3
local txtGeo_Key = iup.text { Value=" " ; Tip="Enter your Geoapify personal API Key code"; } -- V5.3
local lblGeoPlot = iup.label { Title=" Plot Geocoder: " ; Tip="Choose a Maps Geocoder for plotting locations"; } -- V5.3
local lstGeoPlot = iup.list { DropDown="YES"; Visible_Items="9" ; VisibleColumns="9"; Tip="Choose a Maps Geocoder for plotting locations"; "Disabled"; "Google Maps"; "Geoapify"; } -- V5.3 -- V6.0
local lblRegBias = iup.label { Title=" "..StrRegBiasTitle..": " ; Tip="Choose region biasing top level domain code for Geocoder"; }
local lstRegBias = iup.list { DropDown="YES"; Visible_Items="30" ; VisibleColumns="9"; Tip="Choose region biasing top level domain code for Geocoder"; }
local tglGeoCode = iup.toggle { Title=" "..StrGeoCodeTitle..":" ; Tip="Recognise Country/State/Chapman Codes \n and other plotting enhancements"; RightButton="YES"; }
local tglLogging = iup.toggle { Title=" "..StrLoggingTitle..":" ; Tip="Enable logging of plot data to CSV"; RightButton="YES"; } -- V5.3
local lblLocZoom = iup.label { Title=StrMapZoomTitle..": " ; Tip="Current default Zoom level for a selected Map Marker"; }
local txtLocZoom = iup.text { ReadOnly="YES" ; Tip="Current default Zoom level for a selected Map Marker"; }
local lblFromTag = iup.label { Title=StrFromTagTitle..": " ; Tip="Choose Place or Address or both Address ~ Place fields for Locations"; }
local lstFromTag = iup.list { DropDown="YES"; Visible_Items="9" ; Tip="Choose Place or Address or both Address ~ Place fields for Locations"; }
for i = IntFromPLAC, IntFrom_A__P_ do lstFromTag[i] = TblFromName[i] end -- Add the dropdown list values
local lblStorage = iup.label { Title=StrStorageTitle..": " ; Tip="Select the storage option for the Locations Database"; }
local lstStorage = iup.list { DropDown="YES"; Visible_Items="9" ; Tip="Select the storage option for the Locations Database"; "Plugin Data Folder"; "Repository Records"; "Source Records"; "Source & Repository"; }
if fhGetAppVersion() > 5 and IntFromTag == IntFromPLAC then lstStorage[lstStorage.Count+1] = "Place Records" end -- FH6
local timStorage = iup.timer { Time=200; Run="NO"; }
local strMapFolder = "Select the Output Folder for Web Page Maps, and if a\nFamily Tree CD/Website folder then Maps will be integrated"
local txtMapFolder = iup.text { MultiLine="YES"; VisibleLines="2" ; Tip=strMapFolder; WordWrap="YES"; } -- was VisibleLines="3"
local btnMapFolder = iup.button { Title="<< Set Maps Folder" ; Tip=strMapFolder; }
local btnDeleteAll = iup.button { Title=" Delete All Maps " ; Tip="Delete ALL Web Page Map files from the selected Folder"; }
local lblWebMaps = iup.label { Title=" Web Page Maps:" ; Tip="Choose the display mode for the Web Page Maps"; } -- V5.3
local lstWebMaps = iup.list { DropDown="YES"; Visible_Items="9" ; Tip="Choose the display mode for the Web Page Maps"; "Google Maps (Legacy)"; "Leaflet Maps"; "Google Maps (Advanced)"; } -- V6.0
local lblPrivacy = iup.label { Title=StrPrivacyTitle..":" ; Tip="Exclude Individuals from Maps according to Privacy Flags"; }
local lstPrivacy = iup.list { DropDown="YES" ; Tip="Exclude Individuals from Maps according to Privacy Flags"; "Living Flags"; "Private Flags"; "Living & Private"; "Switched Off"; }
local tglFactSet = iup.toggle { Title=StrFactSetTitle..":" ; Tip="Exclude any Facts not listed in the CD/Website pages"; RightButton="YES"; } -- V3.7
local lblMapZoom = iup.label { Title=StrMapZoomTitle..":" ; Tip="Current default Zoom level for a selected Map Marker"; }
local txtMapZoom = iup.text { ReadOnly="YES" ; Tip="Current default Zoom level for a selected Map Marker"; }
local lblMarkers = iup.label { Title=" "..StrMarkersTitle..":" ; Tip="Select how Map Markers are initially placed on Maps"; }
local lstMarkers = iup.list { DropDown="YES" ; Tip="Select how Map Markers are initially placed on Maps"; "No Animation"; "Synchro Drop"; "Cascade Drop"; }
local lblColours = iup.label { Title=" "..StrColoursTitle..":" ; Tip="Choose the Map Markers pin colour on Maps"; } -- V6.0
local lstColours = iup.list { DropDown="YES" ; Tip="Choose the Map Markers pin colour on Maps"; "Red"; "Blue"; "Green"; } -- V6.0
local lblLegends = iup.label { Title="Map Pane Legends:" ; Tip="Choose the legends size for the Plugin Map Pane"; } -- V5.3
local lstLegends = iup.list { DropDown="YES" ; Tip="Choose the legends size for the Plugin Map Pane"; "Tiny"; "Small"; "Medium"; "Large"; "Huge"; } -- V5.3
local btnCopyPLAC = iup.button { Title="Copy Place Database" ; Tip="Copy ALL Standardized, Latitude, and Longitude values \n from the 'Place Fields only' Locations database \n into the ' ~ Place only' Locations database"; }
local btnCopyADDR = iup.button { Title="Copy Address Database" ; Tip="Copy ALL Standardized, Latitude, and Longitude values \n from the 'Address Fields only' Locations database \n into the 'Address ~ only' Locations database"; }
local btnPurgeBOTH = iup.button { Title="Purge Addr ~ Place Database"; Tip="Purge ALL Standardized, Latitude, and Longitude values \n not in current '"..TblFromName[IntFromTag].."' Locations data !!!"; }
local btnErasePLAC = iup.button { Title="Erase Place Database" ; Tip="Erase ALL Standardized, Latitude, and Longitude values \n from the 'Place Fields only' Locations database !!!"; }
local btnEraseADDR = iup.button { Title="Erase Address Database" ; Tip="Erase ALL Standardized, Latitude, and Longitude values \n from the 'Address Fields only' Locations database !!!"; }
local btnEraseBOTH = iup.button { Title="Erase Addr ~ Place Database"; Tip="Erase ALL Standardized, Latitude, and Longitude values \n from the 'Address ~ Place' Locations database !!!"; }
local btnDefault = iup.button { Title="Restore Defaults" ; Tip="Restore default Settings for Window positions/sizes, Tab selection, \n "..StrGeoCodeTitle..", "..StrRegBiasTitle..", "..StrFromTagTitle..", \n "..StrLoggingTitle..", Maps Output Folder, Quota Warning, \n "..StrMapZoomTitle..", "..StrMarkersTitle..", "..StrColoursTitle..", "..StrPrivacyTitle..", and "..StrFactSetTitle; }
local btnSetFont = iup.button { Title="Set Window Fonts" ; Tip="Choose user interface window font style"; }
local timMapPane = iup.timer { Time=500; Run="NO"; } -- Renamed & was Time=900 -- V4.3
-- Create the Main Dialogue Common controls with title/value and tooltip, etc
local lblStatus = iup.label { Title="Status:" ; Tip="Plugin status messages"; }
LblMessage = iup.label { Title=" " ; Tip="Plugin status messages"; }
local btnUpdates = iup.button { Title="Check for Updates" ; Tip="Check for a later version in the Plugin Store"; } -- V6.0
local btnGetHelp = iup.button { Title=" Help && Advice" ; Tip="Obtain online Help and Advice from the Plugin Store"; }
local btnDestroy = iup.button { Title="Close Plugin" ; Tip="Close the Plugin"; }
-- Create the WebBrowser based on its ProgID and connect it to LuaCOM
OLEcontrol = iup.olecontrol { "Shell.Explorer.1"; DesignMode="NO"; }
OLEcontrol:CreateLuaCOM()
-- Create the Geocode Location Plots layout
local vboxPlotEntry= iup.vbox { Margin="1x1";
iup.hbox { Gap=iup_gui.Gap;
iup.vbox { Margin="0x0";
iup.hbox { lblLocation; btnLocation; };
iup.hbox { lblStandard; txtStandard; }; }; btnLocToSub; iup.vbox { Margin="0x0"; btnClearAll; btnClearSub; }; };
iup.hbox { Gap=iup_gui.Gap; lblLatitude; TxtLatitude; btnMoveNorth; btnMoveSouth; btnNextPlot; btnNextSubs; btnClearLatLng; }; -- V4.9
iup.hbox { Gap=iup_gui.Gap; lblLongitude; TxtLongitude; btnMoveWest; btnMoveEast; btnNextDefined; btnNextUnplot; btnSetBlocked; }; -- V4.9
}
local vboxPlotData = iup.vbox { Margin=iup_gui.Margin;
iup.hbox { Gap=iup_gui.Gap; lblPlotStats; iup.hbox{ lblPlotted; lblTentative; lblNoPlot; lblInvalid; lblNoData; lblPlotTotal; Homogeneous="YES"; Gap="0"; }; Margin="2x2"; };
vboxPlotEntry;
}
local framePlotData= iup.frame { Title=" Manage Location Plots ";
vboxPlotData;
}
local vboxGeoLoc = iup.vbox { Gap=iup_gui.Gap;
iup.hbox { Gap="10"; btnPlotAll; btnPlotSome; btnPlotThis; Homogeneous="YES"; Margin="0x6"; };
framePlotData;
}
-- Create the Create Web Page Maps layout
local vboxMapData = iup.vbox { Margin=iup_gui.Margin;
iup.hbox { Gap=iup_gui.Gap; lblMapStats; iup.hbox{ lblMapped; lblOmitted; lblFiltered; lblNoMap; lblMapTotal; Homogeneous="YES"; Gap="0"; }; Margin="2x2"; };
iup.hbox { Gap=iup_gui.Gap; lblIndiName; btnIndiName; btnPrevMapped; btnPrevUnmap; };
iup.hbox { Gap=iup_gui.Gap; lblMapState; txtMapState; btnDeleteMap; btnNextMapped; btnNextUnmap; };
}
local frameMapData = iup.frame { Title=" Manage Individual Maps ";
vboxMapData;
}
local vboxCreate = iup.vbox { Gap="10";
iup.hbox { Gap="10"; btnMapAll; btnMapSome; btnMapThis; Homogeneous="YES"; Margin="0x6"; };
frameMapData;
}
-- Create the Set Preference Options layout
local vboxPlotOpt = iup.vbox { Margin=iup_gui.Margin;
iup.hbox { Gap=iup_gui.Gap; Margin="1x2"; lblAPI_Key; txtAPI_Key; lblGeo_Key; txtGeo_Key; Alignment=":ALEFT"; }; -- V5.3 -- V4.2 -- V4.3
iup.hbox { Gap=iup_gui.Gap; Margin="1x2"; lblGeoPlot; lstGeoPlot; lblRegBias; lstRegBias; }; -- V5.3
iup.hbox { Gap=iup_gui.Gap; Margin="1x2"; tglGeoCode; iup.label{ Expand="HORIZONTAL"; }; tglLogging; iup.label{ Expand="HORIZONTAL"; }; iup.hbox{ lblLocZoom; txtLocZoom; }; }; -- V5.3
iup.hbox { Gap=iup_gui.Gap; Margin="1x2"; iup.hbox{ lblFromTag; lstFromTag; }; iup.hbox{ lblStorage; lstStorage; }; }; -- V5.3
}
local vboxMapsOpt = iup.vbox { Margin=iup_gui.Margin;
iup.hbox { Gap=iup_gui.Gap; Margin="1x1"; txtMapFolder; iup.hbox { btnMapFolder; btnDeleteAll; }; }; -- V5.3
iup.hbox { Gap=iup_gui.Gap; Margin="1x2"; iup.hbox{ lblWebMaps; lstWebMaps; }; iup.hbox{ lblPrivacy; lstPrivacy; }; iup.label{ Expand="HORIZONTAL"; }; tglFactSet; iup.hbox{ lblMapZoom; txtMapZoom; }; }; -- V5.3 -- V6.0
iup.hbox { Gap=iup_gui.Gap; Margin="1x2"; iup.hbox{ lblMarkers; lstMarkers; }; iup.hbox{ lblColours; lstColours; }; iup.hbox{ lblLegends; lstLegends; }; }; -- V4.3 -- V6.0
}
local vboxDataOpt = iup.vbox { Margin=iup_gui.Margin;
iup.hbox { Gap="12"; Margin="0x15"; btnCopyPLAC; btnCopyADDR; btnPurgeBOTH; Homogeneous="YES"; };
iup.hbox { Gap="12"; Margin="0x8" ; btnErasePLAC; btnEraseADDR; btnEraseBOTH; Homogeneous="YES"; };
}
local tabPrefer = iup.tabs { Tip="Select Preference Options for Location Plots, or Web Page Maps, or Database Management"; -- V4.7
vboxPlotOpt; TabTitle0=" Location Plot Options ";
vboxMapsOpt; TabTitle1=" Web Page Map Options ";
vboxDataOpt; TabTitle2=" Database Management ";
}
local vboxPrefer = iup.vbox { Gap=iup_gui.Gap;
tabPrefer;
iup.hbox { Gap="100"; btnDefault; btnSetFont; Homogeneous="YES"; Margin="150x10"; };
}
local intPreferTab = IntTabGeoLoc
-- Create the Tab controls layout
local tabControls = iup.tabs { Tip="Select Geocode plots for Places/Addresses, \n Create HTML web page maps for Individuals, \n or Set Preference Options"; -- V4.7
vboxGeoLoc; TabTitle0=" Geocode Location Plots ";
vboxCreate; TabTitle1=" Create Web Page Maps ";
vboxPrefer; TabTitle2=" Set Preference Options ";
}
-- Combine all the above controls
local allControls = iup.vbox { Margin=iup_gui.Margin;
tabControls;
iup.hbox { Gap="4"; lblStatus; LblMessage; btnUpdates; btnGetHelp; btnDestroy; Margin="18x2"; }; -- was Gap="20" -- V4.2 -- V6.0
iup.hbox { OLEcontrol; MinSize="x440" }; -- If "710" is width, "x500" is max height with no grey bar at top
}
-- Create the Main dialogue
dialogMain = iup.dialog { Title=iup_gui.Plugin..iup_gui.Version..StrTranslate("Output_Language"); -- V6.0 ????
allControls;
}
if fhGetAppVersion() > 6 then -- FH V7 IUP 3.28 -- V4.7
tabPrefer.TabPadding="0x4"
tabControls.TabPadding="0x4"
else -- FH V6 IUP 3.11 -- V4.7
tabPrefer.Padding="8x8"
tabControls.Padding="8x8"
end
-- GUI Trees for Location (PLAC, ADDR, BOTH) and Individual (INDI) Lists --
local tblList = {} -- Table of chosen GUI tree list controls
local tblTree = {} -- Table of all the GUI tree list controls
local strLeaf = nil -- Selected leaf node title
local intLeaf = 0 -- Selected leaf node ident currently
local intNode = 0 -- Selected leaf node ident previously
local function doColorLeafNode(intIdent,strColor) -- Set leaf node colour in tree
if tblList.Tree and intIdent and intIdent > 0 then
if intIdent == intNode then
tblList.Color = strColor -- If highlighted then only updated saved GUI color
else
iup.SetAttributeId(tblList.Tree,"COLOR",intIdent,strColor)
end
end
end -- local function doColorLeafNode
local function doSelectLeafNode(intIdent) -- Save leaf node ident & color and highlight in tree
if tblList.Tree then
if intNode > 0 then -- Restore old leaf color
iup.SetAttributeId(tblList.Tree,"COLOR",intNode,tblList.Color)
end
intLeaf = intIdent -- Save new leaf details
intNode = intIdent
tblList.Tree.Value = intNode
tblList.Color = iup.GetAttributeId(tblList.Tree,"COLOR",intNode)
iup.SetAttributeId(tblList.Tree,"COLOR",intIdent,iup_gui[TblStatusColour[IntHighlight]])
end
end -- local function doSelectLeafNode
local function strGetTreeLeaf(iupTree,intIdent) -- Translate tree node ident into leaf title/userid and color
if tonumber(intIdent) then
if iup.GetAttributeId(iupTree,"KIND",intIdent) == "LEAF" then
local strColor = iup.GetAttributeId(iupTree,"COLOR",intIdent)
if strColor == iup_gui[TblStatusColour[IntHighlight]] then strColor = tblList.Color end
local strLeaf = iup.GetAttributeId(iupTree,"USERID",intIdent) -- "USERID" replaces "TITLE" that may be truncated at 254 chars -- V4.6
strLeaf = tblList.dicLeaf[strLeaf] or strLeaf -- Cater for Reverse Display Order -- V4.9
return strLeaf, strColor
end
end
return nil
end -- local function strGetTreeLeaf
local function doBuildTreeNodes(tblData,strNode,strIndi) -- Build the GUI tree branches & leaves
local intSize = 1 -- Number of Head label characters
local strHead = "" -- Head label of branch node
local intHead = 0 -- Head index of branch node
local tblHead = {} -- Head table of index per branch
local tblNode = {} -- Node table of sorted items for GUI tree
local intNode = 0 -- Node ident of strNode to be found
local dicLeaf = {} -- Lookup for long leaf names -- V4.6
local strColor = iup_gui[TblStatusColour[IntPlotNoData]]
tblList.Color = strColor
if tblList.Tree.Count then
tblList.Tree.DelNode0 = "CHILDREN" -- Ensure GUI tree only has root node
end
tblList.tblLeaf = {} -- Holds the tree leaf names
tblList.dicLeaf = {} -- Holds reverse order translation -- V4.9
for strLeaf, _ in pairs(tblData) do
if tblData[strLeaf].use then -- Leaf chosen for use is true and not nil
tblData[strLeaf].use = false -- V4.9 was nil but false means not true
if not strIndi and StrReverse == "ON" then -- Use reverse Place parts display order -- V4.9
local strBack = fhCallBuiltInFunction("TextPart",strLeaf,1,0,"REV")
tblList.dicLeaf[strBack] = strLeaf -- Set translation back to normal order -- V4.9
strLeaf = strBack
end
table.insert(tblList.tblLeaf,strLeaf) -- Create and sort Leaf table so tree is pre-sorted - also used by Plot All/Map All loops
if #strLeaf >= 254 then -- Workaround for node table limiting leaf names to 254 chars -- V4.6
dicLeaf[strLeaf:sub(1,254)] = strLeaf -- Save long leaf name against truncated leaf name -- V4.6
end
end
end
local strMask = '^[ -/]+' -- Disregard leading Leaf non-alphanumeric common symbols " , space, etc, for sort -- V4.3
table.sort(tblList.tblLeaf,function(strA,strB) return (strA:gsub(strMask,"")) < (strB:gsub(strMask,"")) end ) -- Do not StrEncode_ASCII? -- V4.3
if #tblList.tblLeaf > 1000 then intSize = 4 end -- Increase initial size of Head labels for large lists
repeat
intHead = 0
tblHead = {}
tblNode = {}
for intLeaf, strLeaf in ipairs(tblList.tblLeaf) do -- Increment the Head prefix size for leading ellipsis Leaf nodes
local intPref = intSize + ( (strLeaf:match("^ ~ ") and 1) or 0 )
strHead = strLeaf:gsub(strMask,"") -- Disregard leading Leaf non-alphanumeric common symbols " , space, etc -- V4.3
strHead = strHead:substring(1,intPref) -- Extract Head label prefix chars from Leaf and convert to uppercase ASCII -- 13 Nov 2014
strHead = strHead:upper() -- Do not StrEncode_ASCII? -- V4.3
if not tblHead[strHead] then
tblHead[strHead] = intHead -- Assign Head index to this label
intHead = intHead + 1
end
local intHead = tblHead[strHead] -- Create new Head label branch node if necessary
if not tblNode[intHead] then
tblNode[intHead] = { branchname=strHead; state="COLLAPSED"; color=iup_gui.Black; } -- keys must be lowercase -- V3.6 Color=iup_gui.Black added
end
local strBack = tblList.dicLeaf[strLeaf] or strLeaf -- Cater for Reverse Display Order -- V4.9
local strData = tblData[strBack][tblList.Status] or ""
local strMode = tblData[strBack].mod or "" -- V4.9
strColor = iup_gui[TblStatusColour[IntStatusIndex(strData..strMode,tblList.States)]]
table.insert(tblNode[intHead],{ leafname=strLeaf; color=strColor; }) -- keys must be lowercase -- Insert the Leaf node into branch node
intLeaf = intHead + intLeaf + 1
if strIndi then tblData[strBack].Leaf = intLeaf end -- V4.9 -- V3.6
if strNode then
if strNode == strBack then intNode = intLeaf end -- V4.9
end
end
intSize = intSize + 2
until #tblList.tblLeaf <= 1000 or intHead > 25 -- If large list and few branches (e.g. single name database), then repeat with bigger label
tblList.Tree.AutoRedraw = "NO"
tblList.Dialog:map()
iup.TreeAddNodes(tblList.Tree,tblNode) -- Populate the mapped tree control with branch & leaf nodes
for intIdent = 0, tblList.Tree.Count-1 do -- Workaround for node table limiting leaf names to 254 chars -- V4.6
if iup.GetAttributeId(tblList.Tree,"KIND",intIdent) == "LEAF" then
local strLeaf = iup.GetAttributeId(tblList.Tree,"TITLE",intIdent)
if #strLeaf >= 254 then
strLeaf = dicLeaf[strLeaf:sub(1,254)] -- Lookup long leaf name using truncated leaf name -- V4.6
end
iup.SetAttributeId(tblList.Tree,"USERID",intIdent,strLeaf) -- Set full leaf name as UserId -- V4.6
end
end
tblList.Tree.AutoRedraw = "YES"
if tblList.BtnEx.Title:sub(1,8) == "Contract" then
local intIdent = tblList.Tree.Value
tblList.Tree.ExpandAll = "YES" -- Expand all branches
tblList.Tree.Value = intIdent -- Restore selected leaf node
end
return intNode -- Return ident of strNode if it matched a strLeaf
end -- local function doBuildTreeNodes
local function doShowTreeDialogue(iupButton) -- Show chosen GUI tree list just below its button or at stcky position
local tblPosn = iup_gui.DialogueAttributes("List")
if tblPosn.CoordX == iup.CENTER -- If dialogue is in default central position then...
and tblPosn.CoordY == iup.CENTER then
tblPosn = iup_gui.DialogueAttributes("Main") -- Get X & Y position of the "Main" window
local intPosX = tblPosn.CoordX
local intPosY = tblPosn.CoordY
tblPosn = iupButton.position:splitnumbers() -- Add Y position of button control
intPosY = intPosY + tblPosn[2]
iup_gui.CustomDialogue("List","600x400",intPosX + 30,intPosY + 155)
end
iup_gui.ShowDialogue("List",tblList.Dialog,tblList.BtnNo,"keep")
end -- local function doShowTreeDialogue
local function doBuildTreeDialogues() -- Build all four GUI Trees for PLAC, ADDR, BOTH & INDI and set their positions
local function doGetTreeLeaf(iupTree,intIdent,intStatus) -- Selection call back for tree node
if intStatus == 1 then
intLeaf = intIdent
strLeaf = strGetTreeLeaf(iupTree,intIdent) -- Set selected leaf node ident & title
end
end -- local function doGetTreeLeaf
local function doReverseOrder(iupTree,intState) -- Order toggle action to reverse display order -- V4.9
if intState == 0 then StrReverse = "OFF" end
if intState == 1 then StrReverse = "ON" end
SaveSettings() -- Save sticky data settings
for strLeaf, _ in pairs(TblLocationData) do -- Enable use of locations and rebuild tree nodes -- V4.9
if TblLocationData[strLeaf].use == false then
TblLocationData[strLeaf].use = true
end
end
local strLeaf = strGetTreeLeaf(iupTree,iupTree.Value)
intLeaf = doBuildTreeNodes(TblLocationData,strLeaf)
iupTree.Value = intLeaf -- Restore selected leaf node -- V4.9
end -- local function doReverseOrder
local function doExpandCollapse(iupTree,iupButton) -- BtnEx action to expand/contract tree list
local intIdent = iupTree.Value -- Save current selected leaf node
if iupButton.Title:sub(1,6) == "Expand" then
iupTree.ExpandAll = "YES" -- Expand all branches
iupTree.Value = intIdent -- Restore selected leaf node
intIdent = intIdent - 1
iupButton.Title = iupButton.Title:gsub("Expand","Contract",1)
else
for intIdent = 1, tonumber(iupTree.Count) do -- Contract all branches
if iup.GetAttributeId(iupTree,"KIND",intIdent) == "BRANCH" then
iup.SetAttributeId(iupTree,"STATE",intIdent,"COLLAPSED")
end
end
iupTree.Value = intIdent -- Restore selected leaf node
intIdent = intIdent + 1
iupButton.Title = iupButton.Title:gsub("Contract","Expand",1)
end
iupTree.TopItem = intIdent -- Force selected leaf node well into window
end -- local function doExpandCollapse
local function doMakeTreeDialogue(intTree,strStatus,tblStates,strTree) -- Make one instance of a Tree GUI
local tbl = {}
tbl.Tree = iup.tree { Tip="Select any "..strTree.." to manage the entry"; selection_cb=function(self,intIdent,intStatus) doGetTreeLeaf(self,intIdent,intStatus) end; }
tbl.Order = iup.toggle { Title="Reverse Display Order:" ; Tip="Reverse the Place parts dispay order" ; RightButton="YES"; Value=StrReverse; action=function(self,intState) doReverseOrder(tbl.Tree,intState) end; } -- V4.9
tbl.BtnNo = iup.button { Title="Cancel "..strTree.." selection"; Tip="Cancel the "..strTree.." List selection"; Expand="HORIZONTAL"; action=function() strLeaf = nil return iup.CLOSE end; }
tbl.BtnEx = iup.button { Title="Expand "..strTree.." List" ; Tip="Expand / Contract the List selection" ; Expand="HORIZONTAL"; action=function() doExpandCollapse(tbl.Tree,tbl.BtnEx) end; }
tbl.BtnOk = iup.button { Title="OK "..strTree.." selection" ; Tip="OK the "..strTree.." List selection" ; Expand="HORIZONTAL"; action=function() strLeaf = strGetTreeLeaf(tbl.Tree,intLeaf) return iup.CLOSE end; }
if strStatus == "lat" then
tbl.Vbox = iup.vbox { Alignment="ACENTER"; tbl.Tree; tbl.Order; } -- V4.9
else
tbl.Vbox = iup.vbox { Alignment="ACENTER"; tbl.Tree; } -- V4.9
end
--?IUP3.5 tbl.Vpane = iup.vbox { ExpandChildren="YES"; ... }
tbl.Vpane = iup.vbox { Gap=iup_gui.Gap; Margin=iup_gui.Margin; tbl.Vbox; iup.hbox { tbl.BtnNo; tbl.BtnEx; tbl.BtnOk; Homogeneous="YES"; }; } -- V4.9
tbl.Dialog= iup.dialog { Title=iup_gui.Plugin.." "..strTree.." List"; tbl.Vpane; close_cb=function() strLeaf = nil return iup.CLOSE end; }
tblTree[intTree] = tbl
tblTree[intTree].Status = strStatus
tblTree[intTree].States = tblStates
end -- local function doMakeTreeDialogue
doMakeTreeDialogue(IntFromPLAC,"lat",TblPlotStates,"Place")
doMakeTreeDialogue(IntFromADDR,"lat",TblPlotStates,"Address")
doMakeTreeDialogue(IntFromBOTH,"lat",TblPlotStates,"Address ~ Place")
doMakeTreeDialogue(IntFromINDI,"map",TblMapStates ,"Individual")
end -- local function doBuildTreeDialogues
-- GUI Common Shared Service Data & Functions --
local intLocation = 0 -- Selected entry from Location dropdown list matching StrLocation
local strLocation = "?" -- Saved copies of last mapped Location values
local strLatitude = "?" -- Used only by doMarkMapPanePlot()
local strLongitude = "?"
local strSubValue = "" -- Saved Standardized value before editing
local intSubCount = 0 -- Count of Standardized Location entries
local intLocIndex = 0 -- Saved Location color index before editing
local intNoData = 0 -- Count of never Geocoded Location entries
local intPlotted = 0 -- Count of Lat/Lng Plotted Location entries
local intDefined = 0 -- Count of Lat/Lng Tentative Location entries
local intNoPlot = 0 -- Count of No Plot Found Geocode Location entries
local intInvalid = 0 -- Count of Invalid Request/Request Denied Geocode Location entries
local intUnplotted = 0 -- Count sum of all Location entries with no plot
local tblPlotCounts= { 0; 0; 0; 0; 0; 0; 0; 0; }
local function getCityPlots() -- Get city map plots for current region bias -- V4.7
local dicCityPlots = { -- City map plots for doMarkMapLondon/Single() & doDropStyleMarkers() -- V4.7
au={ Lati=-33.868; Long=151.202; City="Sidney"; };
ca={ Lati=045.422; Long=-75.701; City="Ottawa"; };
nz={ Lati=-41.276; Long=174.764; City="Wellington"; };
uk={ Lati=051.508; Long=-00.131; City="London"; };
us={ Lati=040.713; Long=-74.011; City="New York"; };
}
local strBias = TblRegBias[IntRegBias]:sub(1,2)
local dicPlot = dicCityPlots[strBias]
if not dicPlot then dicPlot = dicCityPlots["uk"] end
return dicPlot.Lati,dicPlot.Long,dicPlot.City
end -- local function getCityPlots
local function setLocation(strLoc,intLoc) -- Set Location name and droplist index -- V4.3
StrLocation = strLoc
intLocation = intLoc
end -- local function setLocation
local function deActivateControls(tblControls) -- Set GUI controls inactive
for _, control in ipairs(tblControls) do
control.Active = "NO"
end
end -- local function deActivateControls
local function doMarkMapPanePlot(strMode) -- Mark the Latitude/Longitude on Map pane
local strLoc = btnLocation.Title
if strLoc..TxtLatitude.Value..TxtLongitude.Value ~= strLocation..strLatitude..strLongitude then
strLocation = strLoc -- Only update Map pane if values have changed
strLatitude = TxtLatitude.Value
strLongitude = TxtLongitude.Value
if strMode == "Move" then
MoveMapPane(strLatitude,strLongitude) -- V4.3 strMode
else
strLoc = strLoc:gsub("&&","&") -- Remove double ampersands "&" from the button title
local strLocName = encoder.StrEncode_XML(strLoc) -- Encode control, &, <, >, " and ' characters -- V4.2
local strCaption = strLoc:gsub(strControlChar," ") -- Make control characters spaces -- V4.2 -- V4.7
MarkMapPane(strLatitude,strLongitude,strLocName,strCaption) -- V4.2
end
end
end -- local function doMarkMapPanePlot
local function doShowWarnings(strButton) -- Show Geocode Plot button and Geocoder Quota limit warnings until inhibitted by user
if StrWarnAns == "Yes" then -- V4.2 New warning
local strWarnAns = GUI_WarnDialogue("WARNING! ADVICE!","Geocoding Location entries requires an API Key and has usage limits.\nIf the 'Status:' shows any 'Geocoder API ...' errors in red then please\nreview the 'Help && Advice' on the 'Map Life Facts FAQ' page.\n Do you want to continue to see this Warning in future ?\n (Click the X Close top right to cancel "..strButton..")")
if strWarnAns == "No" then StrWarnAns = "No" end
if strWarnAns == "X Close" then return false end
end
return true
end -- local function doShowWarnings
-- Show the whole World without Marker
local function doShowWholeWorld() -- V4.3
MarkMapPane("","","")
end -- local function doShowWholeWorld
-- Drop a Marker for Capital City
local function doMarkMapCity() -- V4.3
MarkMapPane(getCityPlots()) -- V4.7 was MarkMapPane(51.500,-0.125,"Central London")
end -- local function doMarkMapCity
-- Refresh the Map Pane in chosen Map Mode
local function setMapMode(intMapMode) -- V4.3 -- V5.3
intMapMode = tonumber(intMapMode) or IntMapMode
IntMapMode = DicMapMode.LeafletMaps -- 2 = Leaflet Maps
CloseMapPane() -- In case earlier instance is running
OpenMapPane() -- Uses WebBrowser OLEcontrol.com:Navigate
SaveSettings() -- Save sticky data settings
end -- local function setMapMode
setMapMode(IntMapMode) -- Update the Map Pane in current Map Mode -- V4.3
progbar.Setup(iup_gui.DialogueAttributes("Bars")) -- Initialise progress bar
-- GUI Geocode Location Plots Tab Functions --
local function intLatLngIndex(strLoc) -- Determine Location color status Index based on Lat/Longitude values
local strMode = TblLocationData[strLoc].mod or ""
local strLat = TblLocationData[strLoc].lat or ""
local strLng = TblLocationData[strLoc].lng or ""
local intLatIndex = IntStatusIndex(strLat..strMode,tblList.States)
local intLngIndex = IntStatusIndex(strLng..strMode,tblList.States)
if intLatIndex == intLngIndex then
return intLatIndex
else
return IntPlotInvalid
end
end -- local function intLatLngIndex
local function doSavePlotData(strOpt) -- Optionally save current Location Standardized value and Lat/Long color Index
if strOpt then
strSubValue = TblLocationData[StrLocation].sub or ""
intLocIndex = intLatLngIndex(StrLocation)
end
end -- local function doSavePlotData
local function doShowPlotData(strOpt) -- Calculate and display Geocoded Location statistics
if strOpt then -- If Standardized/Latitude/Longitude edit then delta adjust for current Location only
if strSubValue ~= "" then intSubCount = intSubCount - 1 end
local strSubValue = TblLocationData[StrLocation].sub or ""
if strSubValue ~= "" then intSubCount = intSubCount + 1 end
tblPlotCounts[intLocIndex] = tblPlotCounts[intLocIndex] - 1
local intLocIndex = intLatLngIndex(StrLocation)
tblPlotCounts[intLocIndex] = tblPlotCounts[intLocIndex] + 1
else -- Otherwise recalculate statistics for entire set of Locations
intSubCount = 0
for intLocIndex = 1, #tblList.States do
tblPlotCounts[intLocIndex] = 0
end
for intLoc, strLoc in ipairs(tblList.tblLeaf) do
strLoc = tblList.dicLeaf[strLoc] or strLoc -- Cater for Reverse Display Order -- V4.9
local strSub = TblLocationData[strLoc].sub or ""
if strSub ~= "" then intSubCount = intSubCount + 1 end
local intLocIndex = intLatLngIndex(strLoc)
tblPlotCounts[intLocIndex] = tblPlotCounts[intLocIndex] + 1
end
end
intNoData = tblPlotCounts[IntPlotNoData]
intPlotted = tblPlotCounts[IntPlotDefined]
intTentative = tblPlotCounts[IntPlotTentative]
intNoPlot = tblPlotCounts[IntPlotNotFound] + tblPlotCounts[IntPlotBlocked] -- V4.9
intInvalid = 0
for intIndex = IntPlotInvalid, #tblList.States do
intInvalid = intInvalid + tblPlotCounts[intIndex]
end
tblPlotCounts[IntPlotInvalid] = intInvalid
intUnplotted = intNoPlot + intInvalid + intNoData
lblPlotted.Title = intPlotted.." Plotted"
lblTentative.Title = intTentative.." Tentative"
lblNoPlot.Title = intNoPlot.." "..StrPlotNoPlot
lblInvalid.Title = intInvalid.." Invalid"
lblNoData.Title = intNoData.." "..StrPlotNoData
lblPlotTotal.Title = #tblList.tblLeaf.." Total"
end -- local function doShowPlotData
local function doRefreshLocation(strLoc,valOpt,strMode) -- Refresh the Location Entry details (valOpt = nil, true, false, "Sub", "Lat" or "Lng")
local strSub = ""
local strLat = ""
local strLng = ""
local strMod = ""
local strGeo = "" -- V5.3+
if valOpt == nil then valOpt = true end
if strLoc then
local tblLoc = TblLocationData[strLoc]
if not tblLoc then -- Fix missing Location Entry due to iup.Tree node 254 char limit -- V4.6
for strLocn, tblLocn in pairs (TblLocationData) do
if strLocn:matches(strLoc) then -- Should never be needed due to fix in strGetTreeLeaf() and doBuildTreeNodes() -- V4.6
if StrLocation == strLoc then StrLocation = strLocn end
strLoc = strLocn
tblLoc = tblLocn
break
end
end
end
if tblLoc then
strSub = tblLoc.sub
strLat = tblLoc.lat
strLng = tblLoc.lng
strMod = tblLoc.mod or ""
strGeo = tblLoc.geo or "" -- V5.3+
ShowStatusMessage(iup_gui.Safe,"OK "..strGeo)
else
MakeNewLocationData(strLoc) -- Fix missing Location Entry should never happen -- V4.6
end
else
strLoc = StrClickHere
end
btnLocation.Title = strLoc:gsub("&","&&") -- Duplicate ampersands "&" so they appear in button title
if valOpt ~= "Sub" then
txtStandard.Value = strSub
end
if valOpt == "Lat" then
TxtLatitude.FgColor = iup_gui[TblStatusColour[IntStatusIndex(TxtLatitude.Value..strMod,TblPlotStates)]]
else
RefreshValue(TxtLatitude, strLat, strMod)
end
if valOpt == "Lng" then
TxtLongitude.FgColor = iup_gui[TblStatusColour[IntStatusIndex(TxtLongitude.Value..strMod,TblPlotStates)]]
else
RefreshValue(TxtLongitude, strLng, strMod)
end
local strColor = TxtLatitude.FgColor
if strColor ~= TxtLongitude.FgColor then
strColor = iup_gui[TblStatusColour[IntPlotInvalid]]
end
btnLocation.FgColor = strColor
txtStandard.FgColor = strColor
doColorLeafNode(intLocation,strColor)
if valOpt then
doSelectLeafNode(intLocation) -- Optionally select Entry in list and Mark on Map pane
if StrLocation then
if valOpt == true then
strLocation = "?"
strLatitude = "?"
strLongitude = "?"
end
if IntTabPosn <= IntTabGeoLoc then -- V4.2
--? ZoomMapPane(IntLocZoom) -- V4.3 needed for Leaflet?
doMarkMapPanePlot(strMode) -- V4.3 strMode
end
else
if IntTabPosn == IntTabPrefer then
doMarkMapCity() -- V4.3
else
doShowWholeWorld() -- V4.3
end
end
end
end -- local function doRefreshLocation
local function doGeoLocTabShared(strOpt,strMode) -- Geocode Location Plots tab actions before & after button functions
local strGeo = "" -- V5.3+
if StrLocation
and TblLocationData
and TblLocationData[StrLocation] then -- Save current Location entry to database -- V3.6
TblLocationData[StrLocation].sub = txtStandard.Value
TblLocationData[StrLocation].lat = TxtLatitude.Value
TblLocationData[StrLocation].lng = TxtLongitude.Value
SaveLocationsData(StrLocation)
doRefreshLocation(StrLocation,strOpt or "Loc",strMode)
strGeo = TblLocationData[StrLocation].geo or "" -- V5.3+
end
if not strOpt and IntStorage == IntStoreFILE then SaveLocationsFile() end
return strGeo -- V5.3+
end -- local function doGeoLocTabShared
local function doGeoLocTabPrefix(strOpt,strMode) -- Geocode Location Plots tab actions before button functions
timMapPane.Run = "NO" -- V4.3
deActivateControls( { allControls; tabControls; btnClearAll; btnClearSub; btnClearLatLng; btnSetBlocked; vboxPlotEntry; btnLocation; btnMoveNorth; btnMoveSouth; btnMoveEast; btnMoveWest; btnNextPlot; btnNextDefined; btnNextSubs; btnNextUnplot; btnPlotThis; btnPlotSome; btnPlotAll; } ) -- V3.5 tabControls added -- V4.9
if strOpt ~= "Lat" then TxtLatitude.Active = "NO" end
if strOpt ~= "Lng" then TxtLongitude.Active = "NO" end
local strGeo = doGeoLocTabShared(strOpt,strMode) -- V5.3+
ShowStatusMessage(iup_gui.Safe,"OK "..strGeo) -- V5.3+
doSavePlotData(strOpt)
end -- local function doGeoLocTabPrefix
local function doGeoLocTabSuffix(strOpt,strMode) -- Geocode Location Plots tab actions after button functions
doGeoLocTabShared(strOpt,strMode) -- V4.3 strMode
doShowPlotData(strOpt) -- Show the Geocoded Location statistics
allControls.Active = "YES"
tabControls.Active = "YES"
if TxtLatitude.FgColor == TxtLongitude.FgColor then
if intPlotted > 0 then -- Enable controls for Plotted entries -- V3.5
btnNextPlot.Active = "YES"
end
if intTentative > 0 then -- Enable controls for Defined entries
btnNextDefined.Active = "YES"
end
if intSubCount > 0 then -- Enable controls for Standardized entries
btnNextSubs.Active = "YES"
end
if intUnplotted > 0 then -- Enable controls for Failed/Invalid/NoData entries
btnNextUnplot.Active = "YES"
end
if TxtLongitude.Value ~= ""
and TxtLatitude.Value ~= "" then -- V4.9
btnClearLatLng.Active = "YES"
end
if TxtLongitude.Value ~= StrPlotBlocked
or TxtLatitude.Value ~= StrPlotBlocked then -- V4.9
btnSetBlocked.Active = "YES"
end
if TxtLatitude.FgColor == iup_gui[TblStatusColour[IntPlotTentative]]
or TxtLatitude.FgColor == iup_gui[TblStatusColour[IntPlotDefined]] then
btnMoveNorth.Active = "YES" -- Enable nudge North & South controls
btnMoveSouth.Active = "YES"
end
if TxtLongitude.FgColor == iup_gui[TblStatusColour[IntPlotTentative]]
or TxtLongitude.FgColor == iup_gui[TblStatusColour[IntPlotDefined]] then
btnMoveEast.Active = "YES" -- Enable nudge East & West controls
btnMoveWest.Active = "YES"
end
btnLocation.Active = "YES"
else
local strColor = iup_gui[TblStatusColour[IntPlotInvalid]]
ShowStatusMessage(strColor,"Erroneous Latitude/Longitude values")
btnLocation.FgColor = strColor
txtStandard.FgColor = strColor
doColorLeafNode(intLocation,strColor)
end
if StrLocation then -- Enable current Location entry controls
vboxPlotEntry.Active = "YES"
txtStandard.Active = "YES"
TxtLatitude.Active = "YES"
TxtLongitude.Active = "YES"
if txtStandard.Value ~= ""
or TxtLongitude.Value ~= ""
or TxtLatitude.Value ~= "" then -- V4.9
btnClearAll.Active = "YES"
end
if txtStandard.Value ~= "" then -- V4.9
btnClearSub.Active = "YES"
end
if IntGeoPlot ~= DicGeoPlot.Disabled then -- Conditional -- V4.3 -- V5.3
btnPlotThis.Active = "YES"
end
end
if IntGeoPlot ~= DicGeoPlot.Disabled then -- Conditional -- V4.3 -- V5.3
btnPlotAll.Active = "YES"
btnPlotSome.Active = "YES"
end
timMapPane.Run = "YES" -- V4.3
end -- local function doGeoLocTabSuffix
local function doReadMapPane() -- Read marker Location or map Zoom level from Map pane -- V4.3
ReadMapPane()
SaveSettings() -- Save sticky data settings -- V5.3
if IntTabPosn == IntTabGeoLoc then
if IntMapLat and IntMapLng then -- Update dragging Marker Location as Defined
if StrLocation then -- V6.0
setLatitudeLongitude(IntMapLat,IntMapLng,StrLocation,"Defined")
local strColor = TxtLatitude.FgColor
btnLocation.FgColor = strColor
txtStandard.FgColor = strColor
TblLocationData[StrLocation].mod = "Defined"
if StrMapAct == "End" then -- Update dragged existing Marker
doGeoLocTabPrefix()
doGeoLocTabSuffix() -- With or without 'DROP' ?
--? doGeoLocTabPrefix(nil,"Move")
--? doGeoLocTabSuffix(nil,"Move")
elseif StrMapAct == "New" then -- Create new plot Location Marker
doGeoLocTabPrefix()
doGeoLocTabSuffix()
end
end
IntMapLat = false
IntMapLng = false
end
elseif IfGotZoom then
if IntTabPosn == IntTabPrefer then
if intPreferTab == IntTabGeoLoc then
IntLocZoom = IntGetZoom -- Update Location Zoom
txtLocZoom.Value = IntLocZoom
end
if intPreferTab == IntTabCreate then
IntMapZoom = IntGetZoom -- Update Web Page Zoom
txtMapZoom.Value = IntMapZoom
end
end
IfGotZoom = false
end
end -- local function doReadMapPane
local function doBuildLocationsList(isDelete) -- Build the Locations list from Database and Gedcom
doSelectLeafNode(0)
ShowStatusMessage(iup_gui.Warn,"Loading Location list, please wait...")
local intFromTag = math.min(IntFromTag,IntFromBOTH)
local strFromTag = TblFromTag[intFromTag]
StrFromTag = strFromTag
tblList = tblTree[intFromTag]
TblLocationData = TblLocationCode[strFromTag]
if next(TblLocationData) == nil or not tblList.Tree.Count or IsFromNew then
IsFromNew = false
local ptrRecord = fhNewItemPtr() -- Either the Database is empty or GUI tree is empty or new Address ~ Place variant
local tblLocations = {}
for _, strRecordType in ipairs({"INDI";"FAM";}) do
ptrRecord:MoveToFirstRecord(strRecordType)
while ptrRecord:IsNotNull() do -- Search all Individual & Family Records for Facts
local ptrFact = fhNewItemPtr()
ptrFact:MoveToFirstChildItem(ptrRecord)
while ptrFact:IsNotNull() do
if fhIsFact(ptrFact) or IsDataTag(ptrFact) then -- Search all Facts for "ADDR" &/or "PLAC" Locations
local strLocName, strLocFull, strLoc_Name, strLoc_Full = StrMakeLocation(ptrFact) --_ 19 June 2013
if strLocFull ~= "" -- Ignore empty "Address ~ Place" variant Location
and intPreferTab == IntTabGeoLoc then -- Ignore unless on Location Plot Options tab
strLocFull = StrControls2SpaceNew(strLocFull) -- New format with newline = bell blob
if not tblLocations[strLocFull] then -- Log every "ADDR ~ PLAC" Location found
tblLocations[strLocFull] = true
end
local strAddr = strLocFull:gsub(" ~ .*"," ~ ")
if not tblLocations[strAddr] then -- Log every "ADDR ~ " Location found
tblLocations[strAddr] = true
end
local strPlac = strLocFull:gsub(".- ~ "," ~ ")
if not tblLocations[strPlac] then -- Log every " ~ PLAC" Location found
tblLocations[strPlac] = true
end
end
-- New for _PLAC tag 19 June 2013
if strLoc_Full ~= "" -- Ignore empty " ~ _Place" variant Location
and intPreferTab == IntTabGeoLoc then -- Ignore unless on Location Plot Options tab
strLoc_Full = StrControls2SpaceNew(strLoc_Full) -- New format with newline = bell blob
local strPlac = " ~ "..strLoc_Full
if not tblLocations[strPlac] then -- Log every " ~ _PLAC" Location found
tblLocations[strPlac] = true
end
end
if strLocName ~= "" then -- Ignore empty Location
strLocName = StrControls2SpaceNew(strLocName) -- New format with newline = bell blob
if not tblLocations[strLocName] then -- Log every Location found
tblLocations[strLocName] = true
end
if not TblLocationData[strLocName] then -- Add new Location to data
MakeNewLocationData(strLocName) -- V3.5
SaveLocationsData(strLocName)
end
TblLocationData[strLocName].use = true -- Location is chosen for use in GUI tree
end
-- New for _PLAC tag 19 June 2013
if strLoc_Name ~= "" then -- Ignore empty Location
strLoc_Name = StrControls2SpaceNew(strLoc_Name) -- New format with newline = bell blob
if not tblLocations[strLoc_Name] then -- Log every Location found
tblLocations[strLoc_Name] = true
end
if not TblLocationData[strLoc_Name] then -- Add new Location to data
MakeNewLocationData(strLoc_Name)
SaveLocationsData(strLoc_Name)
end
TblLocationData[strLoc_Name].use = true -- Location is chosen for use in GUI tree
end
end
ptrFact:MoveNext()
end
ptrRecord:MoveNext()
end
end
for strLocName, tblLocData in pairs(TblLocationData) do
if not tblLocations[strLocName] then -- Delete old Location from data, except for Place Records
if IntStorage == IntStorePLAC then -- FH6
if TblLocationData[strLocName].plc then -- V3.5 needed to remove old Place Record names
TblLocationData[strLocName].use = true
else
TblLocationData[strLocName] = nil -- tblLocData = nil does not always work
end
else
if IntFromTag < IntFromBOTH or isDelete then -- V3.5 only delete Address only and Place only
if IntStorage ~= IntStoreFILE then DeleteLocationRecord(tblLocData.rec) end
TblLocationData[strLocName] = nil -- tblLocData = nil does not always work
else
TblLocationData[strLocName].use = nil -- V3.5 keep Address ~ Place but don't use
end
end
end
end
intLocation = doBuildTreeNodes(TblLocationData,StrLocation)
--? if IntStorage == IntStoreFILE then SaveLocationsFile() end -- Always maintain a database file as backup ?
end
ShowStatusMessage(iup_gui.Safe,"OK")
if intLocation == 0 then
intLocation = tonumber(tblList.Tree.Value)
StrLocation = strGetTreeLeaf(tblList.Tree,intLocation)
end
strLeaf = StrLocation
doRefreshLocation(StrLocation)
end -- local function doBuildLocationsList
local function doGeoLocTabSettings() -- Initial settings for Geocode Location Plots tab
strLocation = "?"
strLatitude = "?"
strLongitude = "?"
ZoomMapPane(IntLocZoom)
doGeoLocTabPrefix()
doBuildLocationsList()
doGeoLocTabSuffix()
end -- local function doGeoLocTabSettings
local function doGeocodePlot(intRange,isConfirm,strClass,isColorOK) -- Plot Range of Locations that match Class Color
doGeoLocTabPrefix()
local isPlottingOK = true
if intRange > 9 then isPlottingOK = doShowWarnings("Plotting Locations") end
if isPlottingOK then
local intLoc = intLocation -- Remember current selected Location
local intStart = intLoc
TxtLatitude.Active = "YES" -- Allow Lat/Long to appear in colour
TxtLongitude.Active = "YES"
dialogMain.Active = "NO"
local isGeocode = true
local isBarStop = false
if not isConfirm then
progbar.Start("Plotting Locations",intRange)
progbar.Message("Plotting Locations ...")
end
local intMax = tonumber(tblList.Tree.Count) -- Cannot traverse tblList.tblLeaf instead, while doRefreshLocation needs GUI tree ident
if intLoc == 0 then intLoc = 1 end
for intCount = 0, intMax do -- Loop once more than number of Locations to return to start Location
if intLoc > intMax then intLoc = 1 end
if intLoc < 1 then intLoc = intMax end
local strLoc, strColor = strGetTreeLeaf(tblList.Tree,intLoc)
if strLoc then
if isColorOK(strColor,strClass) -- Is Location entry Color in correct Class and is not Blocked/No Auto-Geocode -- V4.9
and TblLocationData[strLoc].lat ~= StrPlotBlocked
and TblLocationData[strLoc].lng ~= StrPlotBlocked then
setLocation(strLoc,intLoc) -- V4.3
doRefreshLocation(strLoc,isConfirm) -- Show the Geocoding Location details before plot
if intCount == 0
or intLocation ~= intStart then -- Always include first Location, but skip when start Location reappears
if isConfirm then
isConfirm, isGeocode = GUI_SkipDialogue(strClass)
end
intRange = intRange - 1
if isGeocode then
doSavePlotData("Loc")
if IntStorage ~= IntStorePLAC then -- FH6
TblLocationData[StrLocation].mod = "" -- Cancel any Defined Mode
end
local strAns = StrGeocodeLocation(StrLocation)
doRefreshLocation(StrLocation,isConfirm) -- Show the Geocoding Location details after plot
doShowPlotData("Loc")
if strAns == StrPlotAborted then break end -- V4.2
if isConfirm then
fhSleep(1500,100)
elseif intRange > 1 then
progbar.Start("Plotting Locations",intRange)
progbar.Message("Plotting Locations ...")
end
elseif not isConfirm then
break
end
progbar.Step(1)
collectgarbage() -- Memory garbage collection to avoid 'Not responding' -- V4.9
end
end
isBarStop = progbar.Stop()
if isBarStop then break end
end
if isBarStop then break end
intLoc = intLoc + 1
end
TxtLatitude.Active = "NO"
TxtLongitude.Active = "NO"
doRefreshLocation(StrLocation)
progbar.Close()
dialogMain.Active = "YES"
dialogMain.BringFront = "YES"
end
doGeoLocTabSuffix()
end -- local function doGeocodePlot
function btnPlotAll:action() -- Action for Geocode Plot All Locations button
local intMaximum = #tblList.tblLeaf
local strExclude = " " -- Exclude no Locations
if intPlotted > 0 then
if GUI_WarnDialogue("Geocode Plot All to include Plotted Locations?","Do you wish to include the "..intPlotted.." Plotted Locations in this Geocode Plot All?") ~= "Yes" then
intMaximum = intMaximum - intPlotted
strExclude = iup_gui[TblStatusColour[IntPlotDefined]] -- Exclude only non-Tentative full Plots
end
end
doGeocodePlot( intMaximum, false, strExclude, function(strColor,strExclude) return strColor ~= strExclude end )
end -- function btnPlotAll:action
function btnPlotSome:action() -- Action for Geocode Plot Some Locations button
local intSome, isConfirm = GUI_PlotDialogue(tblPlotCounts)
if intSome > 0 then doGeocodePlot( tblPlotCounts[intSome], isConfirm, iup_gui[TblStatusColour[intSome]], function(strColor,strInclude) return strColor == strInclude end ) end
end -- function btnPlotSome:action
function btnPlotThis:action() -- Action for Geocode Plot This Location button
doGeoLocTabPrefix()
if StrLocation then
TblLocationData[StrLocation].mod = ""
StrGeocodeLocation(StrLocation)
doRefreshLocation(StrLocation)
end
doGeoLocTabSuffix()
end -- function btnPlotThis:action
function btnLocation:action() -- Action for Location button
doGeoLocTabPrefix()
strLeaf = nil
doShowTreeDialogue(btnLocation)
if strLeaf then
setLocation(strLeaf,intLeaf) -- V4.3
doRefreshLocation(StrLocation)
end
doGeoLocTabSuffix()
end -- function btnLocation:action
function btnLocToSub:action() -- Action for Copy Loc to Sub button
doGeoLocTabPrefix()
txtStandard.Value = StrLocation -- Why reset non-tentative lat/long? Removed -- V5.4
--! if TxtLatitude.FgColor ~= iup_gui[TblStatusColour[IntPlotTentative]] then RefreshValue(TxtLatitude, "") end
--! if TxtLongitude.FgColor ~= iup_gui[TblStatusColour[IntPlotTentative]] then RefreshValue(TxtLongitude,"") end
--! TblLocationData[StrLocation].mod = ""
doGeoLocTabSuffix()
end -- function btnLocToSub:action
function txtStandard:action(strChar,strValue) -- Action when Standardized text is edited
doGeoLocTabPrefix("Sub")
TxtLatitude.Value = ""
TxtLongitude.Value = ""
TblLocationData[StrLocation].mod = ""
if strChar == 0 and strValue == txtStandard.Value then
doGeoLocTabSuffix("Sub") -- Delete nothing at left or right special case
strChar = iup.IGNORE
end
return strChar
end -- function txtStandard:action
function txtStandard:valuechanged_cb() -- Call back after Standardized text is edited
doGeoLocTabSuffix("Sub")
end -- function txtStandard:valuechanged_cb
local function doLimitValue(txtLatLong) -- Limit value to 7 decimal places, else pan & zoom fails -- V6.0
local intCaret = tonumber(txtLatLong.Caret)
local fltValue = tonumber(txtLatLong.Value)
if fltValue and #txtLatLong.Value > 10 then
if fltValue < 0 then
fltValue = math.ceil(fltValue*10000000)
else
fltValue = math.floor(fltValue*10000000)
end
txtLatLong.Value = tostring(fltValue/10000000)
txtLatLong.Caret = math.min(intCaret,#txtLatLong.Value+1)
end
end -- local function doLimitValue
function TxtLatitude:action(intChar,strValue) -- Action when Latitude text is edited
doGeoLocTabPrefix("Lat")
local fltValue = tonumber(strValue)
if fltValue then
if fltValue < -90 or fltValue > 90 then
doGeoLocTabSuffix("Lat")
return iup.IGNORE
end
end
TblLocationData[StrLocation].mod = "Defined"
if intChar == 0 and strValue == TxtLatitude.Value
or not strValue:match("^%-?%d*%.?%d*$") then -- Check if optional minus, digits, dot, digits -- V6.0
strLatitude = "?"
doGeoLocTabSuffix("Lat") -- Delete nothing at left or right special case
return iup.IGNORE
end
return intChar
end -- function TxtLatitude:action
function TxtLatitude:valuechanged_cb() -- Call back after Latitude text is edited
doLimitValue(TxtLatitude)
doGeoLocTabSuffix("Lat")
end -- function TxtLatitude:valuechanged_cb
function TxtLongitude:action(intChar,strValue) -- Action when Longitude text is edited
doGeoLocTabPrefix("Lng")
local fltValue = tonumber(strValue)
if fltValue then
if fltValue < -180 or fltValue > 180 then
doGeoLocTabSuffix("Lng")
return iup.IGNORE
end
end
TblLocationData[StrLocation].mod = "Defined"
if intChar == 0 and strValue == TxtLongitude.Value
or not strValue:match("^%-?%d*%.?%d*$") then -- Check if optional minus, digits, dot, digits -- V6.0
strLongitude = "?"
doGeoLocTabSuffix("Lng") -- Delete nothing at left or right special case
return iup.IGNORE
end
return intChar
end -- function TxtLongitude:action
function TxtLongitude:valuechanged_cb() -- Call back after Longitude text is edited
doLimitValue(TxtLongitude)
doGeoLocTabSuffix("Lng")
end -- function TxtLongitude:valuechanged_cb
function btnClearAll:action() -- Action for Clear Entry button
doGeoLocTabPrefix()
txtStandard.Value = ""
TxtLatitude.Value = ""
TxtLongitude.Value = ""
TblLocationData[StrLocation].mod = ""
TblLocationData[StrLocation].geo = "" -- V5.3+
doGeoLocTabSuffix()
end -- function btnClearAll:action
function btnClearSub:action() -- Action for Clear Standardized button
doGeoLocTabPrefix()
txtStandard.Value = ""
doGeoLocTabSuffix()
end -- function btnClearSub:action
function btnClearLatLng:action() -- Action for Clear Lat/Longitude button -- V4.9
doGeoLocTabPrefix()
TxtLatitude.Value = ""
TxtLongitude.Value = ""
TblLocationData[StrLocation].mod = ""
TblLocationData[StrLocation].geo = "" -- V5.3+
doGeoLocTabSuffix()
end -- function btnClearLatLng:action
function btnSetBlocked:action() -- Action for Set Blocked button -- V4.9
doGeoLocTabPrefix()
TxtLatitude.Value = StrPlotBlocked
TxtLongitude.Value = StrPlotBlocked
TblLocationData[StrLocation].mod = ""
TblLocationData[StrLocation].geo = "" -- V5.3+
doGeoLocTabSuffix()
end -- function btnSetBlocked:action
local function doMovePlot(control,intSign,strOpt) -- Move Latitude/Longitude plot
doGeoLocTabPrefix(strOpt)
doReadMapPane()
TblLocationData[StrLocation].mod = "Defined"
TblLocationData[StrLocation].geo = "" -- V5.3+
RefreshValue(control,string.format("%4.7f",control.Value + ( 32 / 2 ^ IntGetZoom ) * intSign),"Defined")
doGeoLocTabSuffix(strOpt,"Move")
end -- local function doMovePlot
function btnMoveNorth:action() -- Action for move N button
doMovePlot(TxtLatitude,1,"Lat")
if tonumber(TxtLatitude.Value) > 90 then RefreshValue(TxtLatitude,90,"Defined") end
end
function btnMoveSouth:action() -- Action for move S button
doMovePlot(TxtLatitude,-1,"Lat")
if tonumber(TxtLatitude.Value) < -90 then RefreshValue(TxtLatitude,-90,"Defined") end
end
function btnMoveEast:action() -- Action for move E button
doMovePlot(TxtLongitude,1,"Lng")
if tonumber(TxtLongitude.Value) > 180 then RefreshValue(TxtLongitude,TxtLongitude.Value-360,"Defined") end
end
function btnMoveWest:action() -- Action for move W button
doMovePlot(TxtLongitude,-1,"Lng")
if tonumber(TxtLongitude.Value) < -180 then RefreshValue(TxtLongitude,TxtLongitude.Value+360,"Defined") end
end
local function doNextLocation(isCondition) -- Find Next/Prior Location that matches Condition
doGeoLocTabPrefix()
local intCount = 0
local intMax = tonumber(tblList.Tree.Count)
local intLoc = intLocation
local strLoc = ""
local tblLoc = {}
local intWay = 1
strKeyState = iup.GetGlobal("MODKEYSTATE") -- State of Shft, Ctrl, Alt, sYs keys in format of 4 chars "SCAY" or space if not pressed
if strKeyState:match("A") then intWay = -1 end -- Alt key reverses direction
repeat
intCount = intCount + 1
intLoc = intLoc + intWay
if intLoc > intMax then intLoc = 1 end
if intLoc < 1 then intLoc = intMax end
strLoc = strGetTreeLeaf(tblList.Tree,intLoc)
if strLoc then
tblLoc = TblLocationData[strLoc]
if isCondition(tblLoc,intLoc) then
setLocation(strLoc,intLoc) -- V4.3
doRefreshLocation(StrLocation)
doGeoLocTabSuffix()
return
end
end
until intCount == intMax
doGeoLocTabSuffix()
end -- local function doNextLocation
function btnNextPlot:action() -- Action for Next Plotted entry button -- V3.5 only traverses 'Plotted' and not 'Defined' locations
doNextLocation( function(tblLoc,intLoc) local strLoc, strColor = strGetTreeLeaf(tblList.Tree,intLoc) return strColor == iup_gui[TblStatusColour[IntPlotDefined]] end ) -- V3.5
end
function btnNextDefined:action() -- Action for Next Defined entry button
doNextLocation( function(tblLoc,intLoc) local strLoc, strColor = strGetTreeLeaf(tblList.Tree,intLoc) return strColor == iup_gui[TblStatusColour[IntPlotTentative]] end )
end
function btnNextSubs:action() -- Action for Next Standardized entry button
doNextLocation( function(tblLoc,intLoc) return ( tblLoc.sub or "" ) ~= "" end )
end
function btnNextUnplot:action() -- Action for Next Unplotted entry button
doNextLocation( function(tblLoc,intLoc) return not IsPlotted(tblLoc) end )
end
-- GUI Create Web Page Maps Tab Functions --
local tblIndiNames = {}
local strIndiName = nil -- Selected entry from Individual dropdown list
local intIndiName = 0
local intNoMap = 0 -- Count of Individuals without Web Page Maps
local intMapped = 0 -- Count of Individuals with Web Page Maps
local intOmitted = 0 -- Count of Individuals with no Facts with Locations
local intFiltered = 0 -- Count of Individuals excluded by Privacy Filter
local intUnmapped = 0 -- Count sum of all Individuals with no Web Page Map
local tblMapCounts = { 0; 0; 0; 0; 0; }
local function doListMapData() -- Show the Mapped/Filtered/Omitted statistics or Folder status
intNoMap = tblMapCounts[IntMapNoMap]
intMapped = tblMapCounts[IntMapFilename]
intOmitted = tblMapCounts[IntMapOmitted]
intFiltered = tblMapCounts[IntMapFiltered]
intUnmapped = intOmitted + intFiltered + intNoMap
lblMapped.Title = intMapped.." Mapped"
lblOmitted.Title = intOmitted.." "..StrMapOmitted
lblFiltered.Title = intFiltered.." "..StrMapFiltered
lblNoMap.Title = intNoMap.." "..StrMapNoMap
lblMapTotal.Title = #tblList.tblLeaf.." Total"
end -- local function doListMapData
local function doPresentMap(strMapName) -- Display map of Individual plot Markers
local strMapFile = StrFolder.."\\"..strMapName
if general.FlgFileExists(strMapFile) then
local intBeg
local intEnd
local strOne
local strTwo
local intLatitude = nil
local intLongitude = nil
local strLocName = nil -- V4.2
local strCaption = nil
local strInfoWin = nil
local intPlot = 0
local strFile = general.StrLoadFromFile(strMapFile)
for strLine in strFile:gmatch("(.-)[\n\r]+") do -- Check map page title refers to correct Individual -- V5.1
intBeg,intEnd,strOne = strLine:find("<title>Map of Locations for (.*)</title>$")
if not strOne then
intBeg,intEnd,strOne = strLine:find(" <title>.- (.*)</title>$") -- V5.1 -- Cater for translated "Map of Locations for"
end
if strOne then
if strOne == encoder.StrEncode_XML(fhGetDisplayText(tblIndiNames[strIndiName].ptr)) then break end -- StrANSI_XML() added in V3.3 on 06 June 2013 -- StrEncode_XML V3.5
ShowStatusMessage(iup_gui.Risk,strMapName.." for "..strOne:gsub("&","&&"))
doShowWholeWorld() -- V4.3
return
end
end
NullMapPane()
ShowStatusMessage(iup_gui.Warn,"Mapping Plot Markers, please wait...")
local doSeek = false
for strLine in strFile:gmatch("(.-)[\n\r]+") do -- V5.1
if not doSeek then -- Skip header of Map file -- V5.1
doSeek = strLine:match("function Initialise")
else
intBeg,intEnd,strOne,strTwo = strLine:find("LatLong%[[0-9]-%] = [Nn]ew.-LatLo?ng%((.-),(.-)%);") -- Match "NewLatLong(" and legacy "new google.maps.LatLng(" -- V4.3
if strOne then intLatitude = tonumber(strOne) end
if strTwo then intLongitude = tonumber(strTwo) end
intBeg,intEnd,strOne = strLine:find("LocName%[[0-9]-%] = '(.*)';") -- V4.2
if strOne then strLocName = strOne end
intBeg,intEnd,strOne = strLine:find("Caption%[[0-9]-%] = '(.*)';")
if strOne then strCaption = strOne end
intBeg,intEnd,strOne = strLine:find("InfoWin%[[0-9]-%] = '(.*)';")
if strOne then strInfoWin = strOne end
strLocName = strLocName or strCaption -- V4.2 while upgrading
if intLatitude and intLongitude and strLocName and strCaption and strInfoWin then
strLocName = strLocName:gsub("\\\\","\\") -- Unescape a backslash character -- V4.2
strCaption = strCaption:gsub("\\\\","\\") -- Unescape a backslash character -- V4.2
strCaption = strCaption:gsub("\\'","'") -- Unescape apostrophe character -- V4.2
PlotMapPane(intLatitude,intLongitude,strLocName,strCaption,strInfoWin) -- V4.2
intLatitude = nil
intLongitude = nil
strLocName = nil -- V4.2
strCaption = nil
strInfoWin = nil
intPlot = intPlot + 1
end
if strLine:find("InitialMap") or intPlot > 30 then break end -- Skip tail of Map file
end
end
ShowStatusMessage(iup_gui.Safe,"OK")
DropMapPane()
end
end -- local function doPresentMap
local function doRefreshIndiName(strIndiName,intStep) -- Refresh the current IndiName Entry details and optionally statistics
local strColor = iup_gui[TblStatusColour[IntMapNoMap]]
if strIndiName then
local strMapState = tblIndiNames[strIndiName].map
local intIndex = IntStatusIndex(strMapState,TblMapStates)
strColor = iup_gui[TblStatusColour[intIndex]]
doColorLeafNode(intIndiName,strColor)
btnIndiName.Title = strIndiName:gsub("&","&&") -- Duplicate ampersands "&" so they appear in button title
btnIndiName.FgColor = strColor
txtMapState.Value = strMapState
txtMapState.FgColor = strColor
if not intStep then
intStep = 0 -- Do not update the statistics
doSelectLeafNode(intIndiName) -- Select leaf node in GUI tree list
if IntTabPosn == IntTabCreate then
if intIndex == IntMapFilename then
doPresentMap(strMapState)
else
doShowWholeWorld() -- V4.3
end
end
end
tblMapCounts[intIndex] = tblMapCounts[intIndex] + intStep -- Optionally update Mapped/Filtered/Omitted statistics
else
btnIndiName.Title = StrClickHere
btnIndiName.FgColor = strColor
txtMapState.Value = ""
txtMapState.FgColor = strColor
end
end -- local function doRefreshIndiName
local function doShowFolderStatus() -- Show status of Map Output Folder
if general.FlgFolderExists(StrFolder) then
ShowStatusMessage(iup_gui.Safe,"OK")
else
ShowStatusMessage(iup_gui.Risk,"Output Folder does not exist")
end
end -- local function doShowFolderStatus
local function doCreateTabPrefix() -- Create Web Page Maps tab actions before button functions
deActivateControls( { allControls; tabControls; btnPrevMapped; btnNextMapped; btnPrevUnmap; btnNextUnmap; btnDeleteMap; btnMapThis; btnMapAll; btnMapSome; vboxCreate; } )
doShowFolderStatus()
end -- local function doCreateTabPrefix
local function doCreateTabSuffix() -- Create Web Page Maps tab actions after button functions
for i = 1, #tblList.States do -- Calculate and show the Mapped/Filtered/Omitted statistics
tblMapCounts[i] = 0
end
for _, strIndiName in ipairs(tblList.tblLeaf) do
local intIndex = IntStatusIndex(tblIndiNames[strIndiName][tblList.Status],tblList.States)
tblMapCounts[intIndex] = tblMapCounts[intIndex] + 1
end
doListMapData()
allControls.Active = "YES"
tabControls.Active = "YES"
if intMapped > 0 then -- Enable controls for Mapped entries
btnPrevMapped.Active = "YES"
btnNextMapped.Active = "YES"
end
if intUnmapped > 0 then -- Enable controls for Omitted/Filtered/NoMap entries
btnPrevUnmap.Active = "YES"
btnNextUnmap.Active = "YES"
end
if general.FlgFolderExists(StrFolder) then -- Enable controls for Delete/Map entries
btnMapSome.Active = "YES"
btnMapAll.Active = "YES"
if txtMapState.FgColor == iup_gui[TblStatusColour[IntMapFilename]] then
btnDeleteMap.Active = "YES"
end
if intIndiName > 0 then
btnMapThis.Active = "YES"
end
end
vboxCreate.Active = "YES"
btnIndiName.Active = "YES"
end -- local function doCreateTabSuffix
local function strMakeIndiName(ptrRecord) -- Make record pointer into Individual list Name
local strIndiName = fhGetItemText(ptrRecord,"~.NAME:SURNAME_FIRST")
local strRecordId = fhGetRecordId(ptrRecord)
if strIndiName:match("^[ ,]*$") then
strIndiName = "[unnamed person]" -- V3.5
end
return strIndiName.." ["..strRecordId.."]"
end -- local function strMakeIndiName
local function doBuildIndiNamesList() -- Build the IndiNames list from Gedcom
ShowStatusMessage(iup_gui.Warn,"Loading Individuals list, please wait...")
tblList = tblTree[IntFromINDI]
if next(tblIndiNames) == nil then
local ptrRecord = fhNewItemPtr()
ptrRecord:MoveToFirstRecord("INDI")
while ptrRecord:IsNotNull() do
local strIndiName = strMakeIndiName(ptrRecord)
tblIndiNames[strIndiName] = {}
tblIndiNames[strIndiName].ptr = ptrRecord:Clone()
tblIndiNames[strIndiName].map = ""
tblIndiNames[strIndiName].use = true -- Individual is chosen for use
ptrRecord:MoveNext("SAME_TAG")
end
local ptrList = fhGetCurrentRecordSel("INDI")
if #ptrList >= 1 then
strIndiName = strMakeIndiName(ptrList[1]) -- Preset to currently selected Individual
end
end
if not tblList.Tree.Count then
ShowStatusMessage(iup_gui.Warn,"Loading Individuals data, please wait...")
if general.FlgFolderExists(StrFolder) then -- Update Map State for every Individual
for strIndiName, tblIndiName in pairs(tblIndiNames) do
local strRecordId = fhGetRecordId(tblIndiNames[strIndiName].ptr)
local strMapName = "map"..strRecordId..".html"
if not general.FlgFileExists(StrFolder.."\\"..strMapName) then strMapName = "" end
tblIndiNames[strIndiName].map = strMapName
end
end
ShowStatusMessage(iup_gui.Warn,"Loading Individuals tree, please wait...")
intIndiName = doBuildTreeNodes(tblIndiNames,strIndiName,"INDI")
end
doShowFolderStatus()
doRefreshIndiName(strIndiName)
end -- local function doBuildIndiNamesList
local function doCreateTabSettings() -- Initial settings for Create Web Page Maps tab
ZoomMapPane(IntMapZoom)
doCreateTabPrefix()
doBuildIndiNamesList()
doCreateTabSuffix()
end -- local function doCreateTabSettings
local function doEraseMapFile(strMapFile) -- Erase web page hyperlinks and delete map file
if general.FlgFileExists(strMapFile) then
-- Does folder contains FH Family Tree CD/Website?
if IsFamilyTreeWebsite() then
-- If so then remove map file hyperlink(s) from web page file
--? local strHyperlink = string.plain("<li><a href='"..strMapFile:gsub("^"..StrFolder:plain().."\\","").."'>Map of Locations for ")..".-</a></li>\n"
local strHyperlink = string.plain("<li><a href='"..strMapFile:gsub("^"..StrFolder:plain().."\\","",1).."'>").."[^\r\n]-</a></li>"..StrEOL -- V5.1 -- Cater for translated "Map of Locations for"
-- Find hyperlink to web page file and extract its file name
--? local strMatch = "<li><a href='([A-Za-z0-9]-%.html)'>Detailed Facts for "
local strMatch = "<li><a href='([indfam0-9]-%.html)'>.-</a></li>" -- V5.1 -- Cater for translated "Detailed Facts for"
for strWebFile in general.StrLoadFromFile(strMapFile):gmatch(strMatch) do -- V5.1
strWebFile = StrFolder.."\\"..strWebFile
local strWebPage = general.StrLoadFromFile(strWebFile)
strWebPage = strWebPage:gsub(strHyperlink,"",1) -- Remove hyperlink to map file
if not strWebPage:match("<li><a href='map%d+%.html'>[^\r\n]-</a></li>"..StrEOL) then -- V5.1 -- No other map file hyperlinks
if strWebPage:match(StrDivSeeAlso..StrOldSeeAlso..StrBegSeeAlso.."<!%-- "..StrEOL) then
strWebPage = strWebPage:gsub(StrOldSeeAlso,"<!-- "..StrNewSeeAlso.." -->",1) -- V5.1 -- Hide 'See also' if page hyperlinks hidden
else
strWebPage = strWebPage:gsub(StrDivSeeAlso..StrOldSeeAlso..StrBegSeeAlso..StrEndSeeAlso,"",1) -- V5.1 -- Remove empty "See also" section
end
end
general.SaveStringToFile(strWebPage,strWebFile)
end
end
general.DeleteFile(strMapFile,ShowDelErr)
end
end -- local function doEraseMapFile
function btnMapAll:action() -- Action for Map All Individuals button
doCreateTabPrefix()
if general.FlgFolderExists(StrFolder) then
local isMappingOK = true
local intIndividuals = tonumber(tblList.Tree.Count)
if intIndividuals > 9 then isMappingOK = doShowWarnings("Web Page Mapping") end
if isMappingOK then
local strMapName = ""
local intIndividual = intIndiName -- Preserve current Individual selected
dialogMain.Active = "NO"
progbar.Start("Web Page Mapping",intIndividuals)
progbar.Message("Web Page Mapping All Individuals ...")
MakeSharedFiles(StrFolder)
SetWebPageMapSections()
for intIndividual = 1, intIndividuals do
progbar.Step(1)
collectgarbage() -- Memory garbage collection to avoid 'Not responding' -- V4.9
local isGeocoderOk = true -- V4.2
local strIndiName = strGetTreeLeaf(tblList.Tree,intIndividual)
if strIndiName then
intIndiName = intIndividual
doRefreshIndiName(strIndiName,-1)
if txtMapState.FgColor == iup_gui[TblStatusColour[IntMapFilename]] then
doEraseMapFile(StrFolder.."\\"..txtMapState.Value) -- Unlink and Delete old Web Page Map file
end
tblIndiNames[strIndiName].map, isGeocoderOk = StrCreateWebPageMap(tblIndiNames[strIndiName].ptr)
doRefreshIndiName(strIndiName,1)
doListMapData()
iup.Flush()
end
if progbar.Stop() or not isGeocoderOk then break end
end
if #tblList.tblLeaf == 1 and txtMapState.FgColor == iup_gui[TblStatusColour[IntMapFilename]] then
strMapName = txtMapState.Value
end
intIndiName = intIndividual -- Restore current Individual selected
doRefreshIndiName(strIndiName)
StrMapPath = StrMapPath..StrFolder.."|" -- Add folder to map paths using new format -- V4.3
progbar.Close()
dialogMain.Active = "YES"
dialogMain.BringFront = "YES"
general.DoExecute(StrFolder.."\\"..strMapName)
end
end
doCreateTabSuffix()
end -- function btnMapAll:action
function btnMapSome:action() -- Action for Map Some Individuals button
doCreateTabPrefix()
if general.FlgFolderExists(StrFolder) then
local isMappingOK = doShowWarnings("Web Page Mapping")
if isMappingOK then
local ptrList = fhPromptUserForRecordSel("INDI")
dialogMain.BringFront = "YES"
if #ptrList > 0 then
local strMapName = ""
local intIndividual = intIndiName -- Preserve current Individual selected
dialogMain.Active = "NO"
progbar.Start("Web Page Mapping",#ptrList)
progbar.Message("Web Page Mapping Some Individuals ...")
MakeSharedFiles(StrFolder)
SetWebPageMapSections()
for _, ptrRecord in ipairs(ptrList) do -- Loop all listed Individual Records
progbar.Step(1)
collectgarbage() -- Memory garbage collection to avoid 'Not responding' -- V4.9
local isGeocoderOk = true -- V4.2
local strIndiName = strMakeIndiName(ptrRecord)
intIndiName = tblIndiNames[strIndiName].Leaf -- V3.6
doRefreshIndiName(strIndiName,-1)
if txtMapState.FgColor == iup_gui[TblStatusColour[IntMapFilename]] then
doEraseMapFile(StrFolder.."\\"..txtMapState.Value) -- Unlink and Delete old Web Page Map file
end
tblIndiNames[strIndiName].map, isGeocoderOk = StrCreateWebPageMap(ptrRecord)
doRefreshIndiName(strIndiName,1)
doListMapData()
iup.Flush()
if progbar.Stop() or not isGeocoderOk then break end -- V4.2
end
if #ptrList == 1 and txtMapState.FgColor == iup_gui[TblStatusColour[IntMapFilename]] then
strMapName = txtMapState.Value
end
intIndiName = intIndividual -- Restore current Individual selected
doRefreshIndiName(strIndiName)
FixOldMapFiles(StrFolder) -- Ensure old map files use new format -- V4.3
progbar.Close()
dialogMain.Active = "YES"
dialogMain.BringFront = "YES"
general.DoExecute(StrFolder.."\\"..strMapName)
end
end
end
doCreateTabSuffix()
end -- function btnMapSome:action
function btnMapThis:action() -- Action for Map This Individual button
doCreateTabPrefix()
if general.FlgFolderExists(StrFolder) then
if intIndiName > 0 then
MakeSharedFiles(StrFolder)
SetWebPageMapSections()
if txtMapState.FgColor == iup_gui[TblStatusColour[IntMapFilename]] then
doEraseMapFile(StrFolder.."\\"..txtMapState.Value) -- Unlink and Delete old Web Page Map file
end
tblIndiNames[strIndiName].map = StrCreateWebPageMap(tblIndiNames[strIndiName].ptr)
doRefreshIndiName(strIndiName)
FixOldMapFiles(StrFolder) -- Ensure old map files use new format -- V4.3
dialogMain.BringFront = "YES"
if txtMapState.FgColor == iup_gui[TblStatusColour[IntMapFilename]] then
general.DoExecute(StrFolder.."\\"..txtMapState.Value) -- Display new Web Page Map file
end
end
end
doCreateTabSuffix()
end -- function btnMapThis:action
function btnDeleteMap:action() -- Action for Delete This Map button
doCreateTabPrefix()
doEraseMapFile(StrFolder.."\\"..txtMapState.Value) -- Unlink and Delete this Web Page Map file
tblIndiNames[strIndiName].map = ""
doRefreshIndiName(strIndiName)
ShowStatusMessage(iup_gui.Safe,"Web Page Map deleted")
doCreateTabSuffix()
end -- function btnDeleteMap:action
function btnIndiName:action() -- Action for Individual button
doCreateTabPrefix()
strLeaf = nil
doShowTreeDialogue(btnIndiName)
if strLeaf then
strIndiName = strLeaf
intIndiName = intLeaf
doRefreshIndiName(strIndiName)
end
doCreateTabSuffix()
end -- function btnIndiName:action
local function doSuccessorIndiName(intWay,isMapped) -- Find Prior/Next Map/No Map Individual
doCreateTabPrefix() -- intWay=-1/+1 & isMapped=1/nil
local intCount = 0
local intMax = tonumber(tblList.Tree.Count)
local intLoc = intIndiName
local strLoc = ""
local tblLoc = {}
repeat
intCount = intCount + 1
intLoc = intLoc + intWay
if intLoc > intMax then intLoc = 1 end
if intLoc < 1 then intLoc = intMax end
strLoc = strGetTreeLeaf(tblList.Tree,intLoc)
if strLoc then
local strMap = tblIndiNames[strLoc].map
if string.find(strMap,"^"..StrMapFilename.."$") == isMapped then
strIndiName = strLoc
intIndiName = intLoc
doRefreshIndiName(strIndiName)
doCreateTabSuffix()
return
end
end
until intCount == intMax
doCreateTabSuffix()
end -- local function doSuccessorIndiName
function btnPrevMapped:action() -- Action for Prior Mapped entry button
doSuccessorIndiName(-1,1)
end
function btnNextMapped:action() -- Action for Next Mapped entry button
doSuccessorIndiName(1,1)
end
function btnPrevUnmap:action() -- Action for Prior Unmapped entry button
doSuccessorIndiName(-1,nil)
end
function btnNextUnmap:action() -- Action for Next Unmapped entry button
doSuccessorIndiName(1,nil)
end
-- GUI Set Preference Options Tab Functions --
local function doPreferTabPrefix() -- Set Preference Options tab actions before button functions
timMapPane.Run = "NO" -- Renamed -- V4.3
deActivateControls( { allControls; tabControls; tabPrefer; vboxPrefer; btnDeleteAll; btnPurgeBOTH; } )
doShowFolderStatus()
end -- local function doPreferTabPrefix
local function doPreferTabSuffix() -- Set Preference Options tab actions after button functions
allControls.Active = "YES"
tabControls.Active = "YES"
tabPrefer .Active = "YES"
vboxPrefer.Active = "YES"
lblAPI_Key.Active = "YES" -- V5.3 -- V4.3
txtAPI_Key.Active = "YES"
lblGeo_Key.Active = "YES" -- V5.3
txtGeo_Key.Active = "YES"
if general.FlgFolderExists(StrFolder) then -- Enable controls for Delete Map entries
btnDeleteAll.Active = "YES"
end
if IntFromTag >= IntFromBOTH then -- Enable Purge Addr ~ Place button and update tooltip
btnPurgeBOTH.Active = "YES"
btnPurgeBOTH.Tip = btnPurgeBOTH.Tip:gsub(" '.+' "," '"..TblFromName[IntFromTag].."' ")
end
timMapPane.Run = "YES" -- Renamed -- V4.3
end -- local function doPreferTabSuffix
local function doDropStyleMarkers() -- Drop example Markers in chosen style
local strInfoWin = "Example marker for" -- V4.7 -- was "1 Recorded Fact at"
local numLatiDelta = 0.020 -- V4.7 -- delta offsets from central plot
local numLongDelta = 0.030
local numLati,numLong,strCity = getCityPlots() -- V4.7 -- lookup city data for current Region Bias
NullMapPane()
PlotMapPane(numLati, numLong, strCity, nil,strInfoWin) -- V4.2 ,nil
PlotMapPane(numLati+numLatiDelta,numLong+numLongDelta,strCity.." (NW)",nil,strInfoWin) -- Style Markers should not change from default Zoom of 12 -- V4.3
PlotMapPane(numLati+numLatiDelta,numLong-numLongDelta,strCity.." (NE)",nil,strInfoWin)
PlotMapPane(numLati-numLatiDelta,numLong-numLongDelta,strCity.." (SE)",nil,strInfoWin)
PlotMapPane(numLati-numLatiDelta,numLong+numLongDelta,strCity.." (SW)",nil,strInfoWin)
DropMapPane(0.03) -- V4.3
end -- local function doDropStyleMarkers
local function doPreferTabSettings(intTab) -- Initial settings for Set Preference Options tab
doPreferTabPrefix()
tabPrefer.ValuePos = intTab - 1 -- 1 Aug 2013
intPreferTab = intTab
if intPreferTab == IntTabGeoLoc then
tblList = tblTree[math.min(IntFromTag,IntFromBOTH)]
ZoomMapPane(IntLocZoom)
if btnLocation.FgColor == iup_gui[TblStatusColour[IntPlotTentative]] then
strLocation = " " -- Ensure location is marked -- V4.2
doMarkMapPanePlot()
else
doMarkMapCity() -- V4.3
end
elseif intPreferTab == IntTabCreate then
doBuildIndiNamesList()
doDropStyleMarkers()
else
doShowWholeWorld() -- V4.3
end
doPreferTabSuffix()
end -- local function doPreferTabSettings
function tabPrefer:tabchangepos_cb(intNew,intOld) -- Call back when Set Preference Options tab position is changed
tabPrefer.Active = "NO"
SaveSettings() -- Save sticky data settings
doPreferTabSettings(intNew + 1) -- was not + 1 -- 1 Aug 2013
end -- function tabPrefer:tabchangepos_cb
function timMapPane:action_cb() -- Call back when Map Pane timer expires -- V4.3
timMapPane.Run = "NO"
doReadMapPane()
timMapPane.Run = "YES"
end -- function timMapPane:action_cb
function txtAPI_Key:action(strChar,strValue) -- Action when Google API Key text is edited -- V4.2
local strVal = strValue:gsub("\n","")
if strVal:match("[^%w-._~]") then -- Only alphanumeric and -._~ unreserved URI characters valid -- V4.3 -- V4.6
local strMessageText = "\n Invalid Google Maps API Key character " .. string.char(strChar) .. " \n"
iup_gui.WarnDialogue( -- V4.3
{ "API Key Invalid" ; iup_gui.Warn; },
{ strMessageText ; iup_gui.Info; "API Key must be alphanumeric"; },
{ " Continue " ; iup_gui.Safe; "Continue & discard character"; }
)
return iup.IGNORE
end
if StrAPI_Key ~= strVal then
StrAPI_Key = strVal
SaveSettings() -- Save sticky data settings
end
if strVal ~= strValue then -- Remove newline characters -- V4.3
txtAPI_Key.value = strVal
return iup.IGNORE
end
return strChar
end -- function txtAPI_Key:action
function txtGeo_Key:action(strChar,strValue) -- Action when Geoapify Key text is edited -- V5.3
local strVal = strValue:gsub("\n","")
if strVal:match("[^%w-._~]") then -- Only alphanumeric and -._~ unreserved URI characters valid -- V4.3 -- V4.6
local strMessageText = "\n Invalid Geoapify API Key character " .. string.char(strChar) .. " \n"
iup_gui.WarnDialogue( -- V4.3
{ "API Key Invalid" ; iup_gui.Warn; },
{ strMessageText ; iup_gui.Info; "API Key must be alphanumeric"; },
{ " Continue " ; iup_gui.Safe; "Continue & discard character"; }
)
return iup.IGNORE
end
if StrGeo_Key ~= strVal then
StrGeo_Key = strVal
SaveSettings() -- Save sticky data settings
end
if strVal ~= strValue then -- Remove newline characters -- V4.3
txtGeo_Key.value = strVal
return iup.IGNORE
end
return strChar
end -- function txtGeo_Key:action
function lstGeoPlot:action(strText,intItem,intState) -- Action when plotting Geocoder is chosen -- V5.3
if intState == 1 then
doPreferTabPrefix()
intItem = intItem - 1
if intItem == DicGeoPlot.Google and IsGoogleKeyMissing()
or intItem == DicGeoPlot.Geoapify and IsGeo_KeyMissing() then
lstGeoPlot.Value = IntGeoPlot + 1
else
IntGeoPlot = intItem
SaveSettings() -- Save sticky data settings
ShowStatusMessage(iup_gui.Safe,"OK") -- V6.0
end
doPreferTabSuffix()
end
end -- function lstGeoPlot:action
function lstRegBias:action(strText,intItem,intState) -- Action when Region Bias Code is chosen
if intState == 1 then
if intItem ~= IntRegBias then
doPreferTabPrefix()
IntRegBias = intItem
SaveSettings() -- Save sticky data settings
doMarkMapCity()
doPreferTabSuffix()
end
end
end -- function lstRegBias:action
function tglGeoCode:action(intState) -- Action for Smart Geocoding toggle
doPreferTabPrefix()
if intState == 0 then StrGeoCode = "OFF" end
if intState == 1 then StrGeoCode = "ON" end
SaveSettings() -- Save sticky data settings
doPreferTabSuffix()
end -- function tglGeoCode:action
local function doAdjustStorage() -- Adjust Database Store when changing to or from 'Place Fields only' mode and IntStorePLAC in use
local intArchive = IntArchive
lstStorage.Value = IntArchive
IntArchive = IntStorage
ConvertLocationsData(IntStorage,intArchive)
IntStorage = intArchive
end -- local function doAdjustStorage
function lstFromTag:action(strText,intItem,intState) -- Action when Locations Source is chosen
if intState == 1 then
if intItem ~= IntFromTag then
doPreferTabPrefix()
if math.min(intItem,IntFromBOTH) ~= math.min(IntFromTag,IntFromBOTH) then
setLocation(nil,0) -- Switching GUI so reset chosen Location -- V4.3
end
if fhGetAppVersion() > 5 then -- FH6
if intItem == IntFromPLAC then
lstStorage.AppendItem = "Place Records"
elseif lstStorage[lstStorage.Count] == "Place Records" then
lstStorage.Value = IntArchive
lstStorage.RemoveItem = lstStorage.Count
end
end
if IntFromTag == IntFromPLAC -- FH6
or intItem == IntFromPLAC then
if IntStorage ~= IntArchive then
doAdjustStorage()
end
end
IntFromTag = intItem
IsFromNew = SetIsFromFlags()
ZoomMapPane(IntLocZoom)
doBuildLocationsList()
SaveSettings() -- Save sticky data settings
doPreferTabSuffix()
end
end
end -- function lstFromTag:action
function lstStorage:action(strText,intItem,intState) -- Action when Database Store is chosen
if intState == 1 then
if intItem ~= IntStorage then
doPreferTabPrefix()
local strRecords = ",\nbut other data you've added to the Records MAY NOT BE PRESERVED"
if IntStorage == IntStorePLAC then strRecords = "" end
if IntStorage == IntStoreFILE then
if intItem == IntStorePLAC then -- FH6
strRecords = ",\nbut may modify the Place Records in your Gedcom data.\nUse 'Tools > Preferences > Map Window' or 'Map > Options...'\nand please tick 'Block Refresh for Non-Tentative Geocodes'" -- V5.4
else
strRecords = ",\nbut will create Source &&/or Repository Records in your Gedcom data"
end
end
local strCitations = ""
if ( IntStorage == IntStoreSOUR and intItem == IntStoreBOTH )
or ( IntStorage == IntStoreBOTH and intItem == IntStoreSOUR ) then
strCitations = ",\nand maintain any associated Citations of your data Source Records"
end
if GUI_WarnDialogue("WARNING! BEWARE!","Changing the Database Storage option will preserve all Geocode data"..strCitations..strRecords..".\n Do you wish to continue with this Database Storage change?") == "Yes" then
ConvertLocationsData(IntStorage,intItem)
IntStorage = intItem
if IntStorage ~= IntStorePLAC
and IntArchive ~= IntStorePLAC then -- V3.5 IntArchive only differs when IntStorePLAC in use
IntArchive = IntStorage
end
IsFromNew = true
doBuildLocationsList()
SaveSettings() -- Save sticky data settings
else
timStorage.Run = "YES" -- See action call back below
end
doPreferTabSuffix()
end
end
end -- function lstStorage:action
function timStorage:action_cb() -- Call back when Database Storage change cancelled timer expires
timStorage.Run = "NO"
lstStorage.Value = IntStorage -- Appears to be only way to ignore & reset drop list selection
end -- function timStorage:action_cb
local function doUpdateMapStatus() -- Update Map State for every Individual
ShowStatusMessage(iup_gui.Warn,"Loading Individuals data, please wait...")
for intIndiName = 1, tonumber(tblList.Tree.Count) do
local strIndiName = strGetTreeLeaf(tblList.Tree,intIndiName)
if strIndiName then
local strRecordId = fhGetRecordId(tblIndiNames[strIndiName].ptr)
local strMapName = "map"..strRecordId..".html"
local strColor = iup_gui[TblStatusColour[IntMapFilename]]
if not general.FlgFileExists(StrFolder.."\\"..strMapName) then
strMapName = ""
strColor = iup_gui[TblStatusColour[IntMapNoMap]]
end
tblIndiNames[strIndiName].map = strMapName
doColorLeafNode(intIndiName,strColor)
end
end
doShowFolderStatus()
doRefreshIndiName(strIndiName)
DicFactSet = {} -- V3.7
end -- local function doUpdateMapStatus
function tglLogging:action(intState) -- Action for Smart Geocoding toggle
doPreferTabPrefix()
if intState == 0 then StrLogging = "OFF" end
if intState == 1 then StrLogging = "ON" end
SaveSettings() -- Save sticky data settings
doPreferTabSuffix()
end -- function tglLogging:action
function txtMapFolder:valuechanged_cb() -- Call back after Output Folder text is edited
doPreferTabPrefix()
local strFolder = txtMapFolder.Value
if StrFolder ~= strFolder then -- V5.1
StrFolder = strFolder
doUpdateMapStatus()
SaveSettings() -- Save sticky data settings
end
doPreferTabSuffix()
end -- function txtMapFolder:valuechanged_cb
function btnMapFolder:action() -- Action for Select Output Folder button
doPreferTabPrefix()
local strFolder = StrFolder
local strDirectory = strFolder
if not general.FlgFolderExists(StrFolder) then strDirectory = iup_gui.PublicPath end
local dialogFolder = iup.filedlg{ dialogtype="DIR", Title="Please select output folder", directory=strDirectory; }
dialogFolder:popup (iup.CENTER, iup.CENTER)
if dialogFolder.status == "-1" then
ShowStatusMessage(iup_gui.Warn,"No folder selected")
else
StrFolder = dialogFolder.Value
if StrFolder ~= strFolder then doUpdateMapStatus() end
SaveSettings() -- Save sticky data settings
end
txtMapFolder.Value = StrFolder
doPreferTabSuffix()
end -- function btnMapFolder:action
function btnDeleteAll:action() -- Action for Delete All Maps button
doPreferTabPrefix()
if GUI_WarnDialogue("WARNING! BEWARE!","All the Web Page Maps will be deleted from the Output Folder.\n Do you wish to continue to Delete these Web Page Maps?") == "Yes" then
for _, dicFile in ipairs(general.GetFolderContents(StrFolder,false)) do -- V6.0
local strFile = dicFile.path
local strPath, strName, strType = general.SplitFilename(strFile)
if string.find(strName,"^"..StrMapFilename.."$") then doEraseMapFile(strFile) end -- Unlink and Delete every Web Page Map file
if string.find(strName,"^mapfacts%.[a-z]+$") then general.DeleteFile(strFile,ShowDelErr) end -- Delete mapfacts.css & mapfacts.js & mapfacts.png files
end
for strIndiName, tblIndiName in pairs(tblIndiNames) do
if strIndiName then tblIndiNames[strIndiName].map = "" end
end
doRefreshIndiName(strIndiName)
ShowStatusMessage(iup_gui.Safe,"Web Page Maps deleted")
end
doPreferTabSuffix()
end -- function btnDeleteAll:action
function lstWebMaps:action(strText,intItem,intState) -- Action when Web Page Maps option is chosen -- V5.3
if intState == 1 then
doPreferTabPrefix()
if IsGoogleMapAndNoKey(0,intItem) then -- V6.0
intItem = DicMapMode.LeafletMaps
lstWebMaps.Value = intItem
end
IntWebMaps = intItem -- 1 = Google Maps (Legacy), 2 = Leaflet Maps, 3 = Google Maps (Advanced)
SaveSettings() -- Save sticky data settings
ShowStatusMessage(iup_gui.Safe,"OK") -- V6.0
doPreferTabSuffix()
end
end -- function lstWebMaps:action
function lstColours:action(strText,intItem,intState) -- Action when Map Pin Colours option is chosen -- V6.0
if intState == 1 then
doPreferTabPrefix()
IntColours = intItem -- 1 = Red, 2 = Blue, 3 = Green
StrColours = DicColours[IntColours]
SaveSettings() -- Save sticky data settings
setMapMode()
ZoomMapPane(IntMapZoom) -- Preserve map zoom
doDropStyleMarkers()
doPreferTabSuffix()
end
end -- function lstMapMode:action
function lstLegends:action(strText,intItem,intState) -- Action when Plugin Map Legends option is chosen -- V5.3
if intState == 1 then
doPreferTabPrefix()
IntLegends = intItem -- 1 = Tiny, 2 = Small, 3 = Medium, 4 = Large, 5 = Huge
SaveSettings() -- Save sticky data settings
setMapMode()
ZoomMapPane(IntMapZoom) -- Preserve map zoom
doDropStyleMarkers()
doPreferTabSuffix()
end
end -- function lstLegends:action
function lstMarkers:action(strText,intItem,intState) -- Action when Markers Style is chosen
if intState == 1 then
doPreferTabPrefix()
IntMarkers = intItem -- 1 = No Animation, 2 = Synchro Drop, 3 = Cascade Drop
SaveSettings() -- Save sticky data settings
doDropStyleMarkers()
doPreferTabSuffix()
end
end -- function lstMarkers:action
function lstPrivacy:action(strText,intItem,intState) -- Action when Privacy Filter is chosen
if intState == 1 then
IntPrivacy = intItem
SaveSettings() -- Save sticky data settings
end
end -- function lstPrivacy:action
function tglFactSet:action(intState) -- Action for Facts Filter toggle -- V3.7
doPreferTabPrefix()
if intState == 0 then StrFactSet = "OFF" end
if intState == 1 then StrFactSet = "ON" end
SaveSettings() -- Save sticky data settings
doPreferTabSuffix()
end -- function tglFactSet:action
local function strRecords() -- Return text for delete data in Records message
if IntStorage == IntStoreFILE then
return ""
else
return "\n and delete any other data that you may have added to the Location Records"
end
end -- local function strRecords
local function doCopyLocations(intFromTag) -- Copy Place or Address Database to Address ~ Place Database
doPreferTabPrefix()
local strSourceName = TblFromName[intFromTag]
local strTargetName = TblFromName[intFromTag - IntFromPLAC + IntFromBOTH]
if GUI_WarnDialogue("WARNING! BEWARE!","Copying the '"..strSourceName.."' Locations Database will overwrite ALL Geocode data"..strRecords().."\n in all the '"..strTargetName.."' entries in the 'Address ~ Place' Locations database.\n\n Do you wish to continue to Copy the '"..strSourceName.."' Locations Database?\n") == "Yes" then
local strSaveTag = StrFromTag -- Save original StrFromTag
StrFromTag = TblFromTag[IntFromBOTH]
TblLocationData = TblLocationCode[StrFromTag] -- Target Address ~ Place database
local strPrefix = ""
local strSuffix = " ~ "
if intFromTag == IntFromPLAC then -- Set " ~ " prefix/suffix for Location name in Address ~ Place database
strPrefix = " ~ "
strSuffix = ""
end
for strLocName, tblLocData in pairs(TblLocationCode[TblFromTag[intFromTag]]) do
strLocName = StrControls2SpaceNew(strLocName) -- New format with newline = bell blob -- V3.5
strLocName = strPrefix..strLocName..strSuffix
if not TblLocationData[strLocName] then -- Add new target Location to data
MakeNewLocationData(strLocName) -- V3.5
end
TblLocationData[strLocName].sub = tblLocData.sub or ""
TblLocationData[strLocName].lat = tblLocData.lat or ""
TblLocationData[strLocName].lng = tblLocData.lng or ""
TblLocationData[strLocName].mod = tblLocData.mod or ""
TblLocationData[strLocName].geo = tblLocData.geo or "" -- V5.3+
SaveLocationsData(strLocName)
end
StrFromTag = strSaveTag -- Restore saved StrFromTag
TblLocationData = TblLocationCode[StrFromTag]
ShowStatusMessage(iup_gui.Safe,"'"..strSourceName.."' Database Copied")
fhUpdateDisplay()
end
IsFromNew = true -- V3.5
doPreferTabSuffix()
end -- local function doCopyLocations
function btnCopyPLAC:action() -- Action for Copy Place Database button
doCopyLocations(IntFromPLAC)
end -- function btnCopyPLAC:action
function btnCopyADDR:action() -- Action for Copy Address Database button
doCopyLocations(IntFromADDR)
end -- function btnCopyADDR:action
function btnPurgeBOTH:action() -- Action for Purge Addr ~ Place Database button
doPreferTabPrefix()
if IntFromTag >= IntFromBOTH then
local strFromName = TblFromName[IntFromTag]
if GUI_WarnDialogue("WARNING! BEWARE!","Purging the '"..strFromName.."' Locations Database will ERASE ALL Geocode data"..strRecords().."\n for all those Location entries not currently included in the Location list.\n\n Do you wish to continue to Purge the '"..strFromName.."' Locations Database?\n") == "Yes" then
IsFromNew = SetIsFromFlags()
doBuildLocationsList("Delete") -- Rebuild list without Locations not included
ShowStatusMessage(iup_gui.Safe,"'"..strFromName.."' Database Purged")
fhUpdateDisplay()
end
end
doPreferTabSuffix()
end -- function btnPurgeBOTH:action
local function doEraseLocation(intFromTag) -- 1="PLAC" or 2="ADDR" or 3="BOTH"
doPreferTabPrefix()
local strFromTag = TblFromTag[intFromTag]
local intFromName = intFromTag -- V4.0
if intFromName == IntFromBOTH then intFromName = IntFromAP end
local strFromName = TblFromName[intFromName] -- V4.0
if GUI_WarnDialogue("WARNING! BEWARE!","Erasing the '"..strFromName.."' Locations Database will ERASE ALL Geocode data"..strRecords()..".\n\n Do you wish to continue to Erase the '"..strFromName.."' Locations Database?\n") == "Yes" then
if IntStorage == IntStoreFILE
or IntStorage == IntStorePLAC then -- V3.5
TblLocationCode[strFromTag] = {}
SaveLocationsFile()
else
TblLocationData = TblLocationCode[strFromTag]
for strLocName, tblLocData in pairs(TblLocationData) do
DeleteLocationRecord(tblLocData.rec)
end
TblLocationCode[strFromTag] = {}
end
ShowStatusMessage(iup_gui.Safe,"'"..strFromName.."' Database Erased")
fhUpdateDisplay()
tblTree[intFromTag].Tree.DelNode0 = "CHILDREN" -- V3.6
if math.min(IntFromTag,IntFromBOTH) == math.min(intFromTag,IntFromBOTH) then
TblLocationData = TblLocationCode[TblFromTag[IntFromTag]]
setLocation(nil,0) -- V3.6 -- V4.3
-- IsFromNew = true --?
end
end
doPreferTabSuffix()
end -- local function doEraseLocation
function btnErasePLAC:action() -- Action for Erase Place Database button
doEraseLocation(IntFromPLAC)
end -- function btnErasePLAC:action
function btnEraseADDR:action() -- Action for Erase Address Database button
doEraseLocation(IntFromADDR)
end -- function btnEraseADDR:action
function btnEraseBOTH:action() -- Action for Erase Addr ~ Place Database button
doEraseLocation(IntFromBOTH)
end -- function btnEraseBOTH:action
local function setControls() -- Reset GUI control values
txtAPI_Key.Value = StrAPI_Key -- V4.2
lstGeoPlot.Value = IntGeoPlot + 1 -- V5.3
txtGeo_Key.Value = StrGeo_Key -- V5.3
lstRegBias.Value = IntRegBias
tglGeoCode.Value = StrGeoCode
txtLocZoom.Value = IntLocZoom
lstFromTag.Value = IntFromTag
tglLogging.Value = StrLogging
lstStorage.Value = IntStorage
txtMapFolder.Value= StrFolder
lstWebMaps.Value = IntWebMaps -- V5.3
lstColours.Value = IntColours -- V6.0
lstLegends.Value = IntLegends -- V5.3
txtMapZoom.Value = IntMapZoom
lstMarkers.Value = IntMarkers
lstPrivacy.Value = IntPrivacy
tglFactSet.Value = StrFactSet -- V3.7
btnLocation.Size = txtStandard.Size:gsub("x[0-9]+","")
btnIndiName.Size = txtMapState.Size:gsub("x[0-9]+","") + btnDeleteMap.Size:gsub("x[0-9]+","")
SaveSettings() -- Save sticky data settings
end -- local function setControls
function btnDefault:action() -- Action for Restore Defaults button
local strReverse = StrReverse
timMapPane.Run = "NO" -- Renamed -- V4.3
ResetDefaultSettings()
if lstStorage[lstStorage.Count] ~= "Place Records" then
lstStorage.AppendItem = "Place Records" -- FH6
end
if IntArchive > IntStorage then -- FH6
doAdjustStorage()
end
tabControls.ValuePos = math.max(0,IntTabPosn - 1) -- Adjust tab selection 10 Nov 2014
setControls() -- Reset controls & redisplay Main dialogue
iup_gui.ShowDialogue("Main")
iup_gui.DefaultDialogue() -- V6.1
setMapMode(IntMapMode) -- V4.3
doGeoLocTabSettings()
SaveSettings() -- Save sticky data settings
end -- function btnDefault:action
function btnSetFont:action() -- Action for User Interface Font button
doPreferTabPrefix()
btnSetFont.Active = "NO"
iup_gui.FontDialogue(tblControls)
SaveSettings() -- Save sticky data settings
doPreferTabSuffix()
btnSetFont.Active = "YES"
end -- function btnSetFont:action
-- GUI Main Dialogue Global Functions --
local function showTabControls(intPrefer) -- Show current tab details -- V4.2
if IntTabPosn == IntTabGeoLoc then
doGeoLocTabSettings()
elseif IntTabPosn == IntTabCreate then
doCreateTabSettings()
elseif IntTabPosn == IntTabPrefer then
doPreferTabSettings(intPrefer) -- 1 Aug 2013
end
end -- local function showTabControls
local strAPI_Key = StrAPI_Key -- V4.2
function tabControls:tabchangepos_cb(intNew,intOld) -- Call back when Main tab position is changed
timMapPane.Run = "NO" -- Renamed -- V4.3
tabControls.Active = "NO"
if strAPI_Key ~= StrAPI_Key -- Has the API Key been changed -- V4.2
and general.FlgFileExists(StrFolder.."\\mapfacts.js") then
MakeSharedFiles(StrFolder) -- Update mapfacts.js with new API Key -- V4.2
strAPI_Key = StrAPI_Key
end
IntTabPosn = intNew + 1 -- 1 Aug 2013
showTabControls(intOld + 1) -- V4.2
if IntStorage == IntStoreFILE then SaveLocationsFile() end
SaveSettings() -- Save sticky data settings
end -- function tabControls:tabchangepos_cb
local function doExecute(strExecutable, strParameter) -- Invoke FH Shell Execute API -- V4.7
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 function doGetHelp(intPage) -- Display the chosen Help page -- V4.8
local strHelp = "https://pluginstore.family-historian.co.uk/page/help/map-life-facts"
local arrHelp = { "-geocode-location-plots-tab"; "-create-web-page-maps-tab"; "-set-preference-options-tab"; "-geocoding-and-mapping"; }
local strPage = arrHelp[intPage] or ""
doExecute( strHelp..strPage )
fhSleep(3000,500)
dialogMain.BringFront="YES"
end -- local function doGetHelp
function btnUpdates:action() -- Action for Check for Updates button -- V6.0
local strFile = iup_gui.MachinePath.."\\VersionInStore Map Life Facts.dat"
general.DeleteFile(strFile)
iup_gui.CheckVersionInStore(true) -- Notify if later Version
end -- function btnUpdates:action
function btnGetHelp:action() -- Action for Help & Advice button according to current tab -- V4.8
doGetHelp(IntTabPosn)
end -- function btnGetHelp:action
function dialogMain:show_cb(intState) -- Call back when GUI window shown, etc -- V4.2
if intState == iup.SHOW then -- IUP_SHOW = 0
allControls.Active = "NO"
if not TblLocationData then
fhSleep(100) -- Update current tab details after Plugin window displays -- V4.2 -- V4.9
LoadLocationsData()
end
fhSleep(100)
doBuildLocationsList()
if IsSplashOn == "API" then -- API Splash is shown first -- V5.3
IsSplashOn = "Yes"
elseif IsSplashOn == "Yes" then -- Display the Splash Main Help -- V4.9
IsSplashOn = "No"
IntTabPosn = IntTabGeoLoc -- Set tab Geocode Location Plots -- V4.2
tabControls.ValuePos = IntTabPosn - 1
doGetHelp(0) -- Splash Main Help for new user -- V4.2 -- V4.8
dialogMain.BringFront = "YES"
end
showTabControls(IntTabGeoLoc)
allControls.Active = "YES"
end
end -- function dialogMain:show_cb
local function doCloseMain()
timMapPane.Run = "NO" -- Renamed -- V4.3
dialogMain.show_cb = nil -- Needed to prevent FH hanging in Plugin debug mode -- V4.2
fhSleep(100)
if IntTabPosn == IntTabGeoLoc then doGeoLocTabPrefix() end
if IntTabPosn == IntTabCreate then doCreateTabPrefix() end
if IntTabPosn == IntTabPrefer then doPreferTabPrefix() end
if IntStorage == IntStoreFILE
or IntArchive == IntStoreFILE then SaveLocationsFile() end -- V3.5
SavePlotLogFileCSV() -- V5.3
CloseMapPane()
end -- local function doCloseMain
function btnDestroy:action() -- Action for Close Plugin button
doCloseMain()
return iup.CLOSE
end -- function btnDestroy:action
function dialogMain:close_cb() -- Call back when GUI window Close pressed
if allControls.Active == "NO" then
allControls.Active = "YES" -- Needed in case of faults that leave controls inactive
timMapPane.Run = "NO" -- Added -- V4.3
return iup.IGNORE
else
doCloseMain()
return iup.CLOSE
end
end -- function dialogMain:close_cb
-- GUI Main Dialogue Initialisation --
--! iup_gui.ShowDialogue("Main",dialogMain,btnDestroy,"map") -- Map dialogue to ensure all controls display correctly
iup_gui.ShowDialogue("Main",dialogMain,nil,"map") -- Map dialogue to ensure all controls display correctly -- Inhibit Close on Return & Esc -- V6.0
tabControls.ValuePos = math.max(0,IntTabPosn - 1) -- Adjust tab selection -- 1 & 27 Aug 2013
local strRegBiasTip = lstRegBias.Tip -- Populate Region Bias list
lstRegBias.Tip = nil -- Temporarily erase tooltip to speed up appenditem
lstRegBias.AutoRedraw = "NO"
lstRegBias.RemoveItem = nil
for _, strRegBias in ipairs(TblRegBias) do
lstRegBias.AppendItem = strRegBias
end
lstRegBias.AutoRedraw = "YES"
lstRegBias.Tip = strRegBiasTip
lstRegBias.Value = IntRegBias
doBuildTreeDialogues() -- Add extra controls
local strRight = "ARIGHT:ABOTTOM"
-- Set other GUI control attributes
tblControls = { { "Font"; "FgColor"; "Alignment"; "Size"; "Expand"; "Active"; "Tip"; {"TipBalloon";"Balloon"}; {"help_cb";function() doGetHelp(0) end;}; setControls; };
[vboxGeoLoc] = { "FontBody"; "Body"; "ARIGHT" ; false; false ; "YES" }; -- Geocode Location Plots controls
[btnPlotAll] = { "FontBody"; "Safe"; false ; false; "HORIZONTAL"; false };
[btnPlotSome] = { "FontBody"; "Safe"; false ; false; "HORIZONTAL"; false };
[btnPlotThis] = { "FontBody"; "Safe"; false ; false; "HORIZONTAL"; false };
[framePlotData]= { "FontHead"; "Head"; false ; false; false ; "YES" };
[vboxPlotData] = { "FontBody"; "Body"; };
[lblPlotStats] = { "FontBody"; "Body"; strRight ; "048"; false ; "YES" };
[lblPlotted] = { "FontBody"; TblStatusColour[IntPlotDefined]
; "ACENTER"; false; "HORIZONTAL"; "YES" };
[lblTentative] = { "FontBody"; TblStatusColour[IntPlotTentative]
; "ACENTER"; false; "HORIZONTAL"; "YES" };
[lblNoPlot] = { "FontBody"; TblStatusColour[IntPlotNotFound]
; "ACENTER"; false; "HORIZONTAL"; "YES" };
[lblInvalid] = { "FontBody"; TblStatusColour[IntPlotInvalid]
; "ACENTER"; false; "HORIZONTAL"; "YES" };
[lblNoData] = { "FontBody"; TblStatusColour[IntPlotNoData]
; "ACENTER"; false; "HORIZONTAL"; "YES" };
[lblPlotTotal] = { "FontBody"; TblStatusColour[IntPlotSumTotal]
; "ACENTER"; false; "HORIZONTAL"; "YES" };
[lblLocation] = { "FontBody"; "Body"; strRight ; "048"; false ; "YES" };
[btnLocation] = { "FontBody"; "Safe"; "ALEFT" ; false; "HORIZONTAL"; "YES" };
[lblStandard] = { "FontBody"; "Body"; strRight ; "048"; false ; "YES" };
[txtStandard] = { "FontBody"; "Safe"; false ; false; "HORIZONTAL"; false };
[lblLatitude] = { "FontBody"; "Body"; strRight ; "048"; false ; "YES" };
[TxtLatitude] = { "FontBody"; "Safe"; false ; false; "HORIZONTAL"; false };
[lblLongitude] = { "FontBody"; "Body"; strRight ; "048"; false ; "YES" };
[TxtLongitude] = { "FontBody"; "Safe"; false ; false; "HORIZONTAL"; false };
[btnLocToSub] = { "FontBody"; "Safe"; false ; "078"; false ; false };
[btnClearAll] = { "FontBody"; "Safe"; "ALEFT" ; "078"; false ; false };
[btnClearSub] = { "FontBody"; "Safe"; "ALEFT" ; "078"; false ; false };
[btnClearLatLng]={ "FontBody"; "Safe"; "ALEFT" ; "078"; false ; false }; -- V4.9
[btnSetBlocked]= { "FontBody"; TblStatusColour[IntPlotBlocked]
; "ALEFT" ; "078"; false ; false }; -- V4.9
[btnNextPlot] = { "FontBody"; "Safe"; false ; "078"; false ; false };
[btnNextDefined]={ "FontBody"; "Safe"; false ; "078"; false ; false };
[btnNextSubs] = { "FontBody"; "Safe"; false ; "078"; false ; false };
[btnNextUnplot]= { "FontBody"; "Safe"; false ; "078"; false ; false };
[btnMoveNorth] = { "FontBody"; "Safe"; false ; "012"; false ; false };
[btnMoveSouth] = { "FontBody"; "Safe"; false ; "012"; false ; false };
[btnMoveWest] = { "FontBody"; "Safe"; false ; "012"; false ; false };
[btnMoveEast] = { "FontBody"; "Safe"; false ; "012"; false ; false };
[vboxCreate] = { "FontBody"; "Body"; "ARIGHT" ; false; false ; "YES" }; -- Create Web Page Maps controls
[btnMapAll] = { "FontBody"; "Safe"; false ; false; "HORIZONTAL"; false };
[btnMapSome] = { "FontBody"; "Safe"; false ; false; "HORIZONTAL"; false };
[btnMapThis] = { "FontBody"; "Safe"; false ; false; "HORIZONTAL"; false };
[frameMapData] = { "FontHead"; "Head"; false ; false; false ; "YES" };
[vboxMapData] = { "FontBody"; "Body"; };
[lblMapStats] = { "FontBody"; "Body"; strRight ; "048"; false ; "YES" };
[lblMapped] = { "FontBody"; TblStatusColour[IntMapFilename]
; "ACENTER"; false; "HORIZONTAL"; "YES" };
[lblOmitted] = { "FontBody"; TblStatusColour[IntMapOmitted]
; "ACENTER"; false; "HORIZONTAL"; "YES" };
[lblFiltered] = { "FontBody"; TblStatusColour[IntMapFiltered]
; "ACENTER"; false; "HORIZONTAL"; "YES" };
[lblNoMap] = { "FontBody"; TblStatusColour[IntMapNoMap]
; "ACENTER"; false; "HORIZONTAL"; "YES" };
[lblMapTotal] = { "FontBody"; TblStatusColour[IntMapSumTotal]
; "ACENTER"; false; "HORIZONTAL"; "YES" };
[lblIndiName] = { "FontBody"; "Body"; strRight ; "048"; false ; "YES" };
[btnIndiName] = { "FontBody"; "Body"; "ALEFT" ; false; "HORIZONTAL"; "YES" };
[lblMapState] = { "FontBody"; "Body"; strRight ; "048"; false ; "YES" };
[txtMapState] = { "FontBody"; "Body"; false ; false; "HORIZONTAL"; "YES" };
[btnDeleteMap] = { "FontBody"; "Risk"; false ; "078"; false ; false };
[btnPrevMapped]= { "FontBody"; "Safe"; false ; "078"; false ; false };
[btnNextMapped]= { "FontBody"; "Safe"; false ; "078"; false ; false };
[btnPrevUnmap] = { "FontBody"; "Safe"; false ; "078"; false ; false };
[btnNextUnmap] = { "FontBody"; "Safe"; false ; "078"; false ; false };
[tabPrefer] = { "FontHead"; "Head"; false ; false; false ; "YES" }; -- Set Preference Options controls
[vboxPlotOpt] = { "FontBody"; "Body"; };
[lblAPI_Key] = { "FontBody"; "Body"; strRight ; "065"; false ; false }; -- V4.2 -- V4.3
[txtAPI_Key] = { "FontBody"; "Safe"; false ; "160"; "HORIZONTAL"; false }; -- V4.2
[lblGeo_Key] = { "FontBody"; "Body"; strRight ; "050"; "HORIZONTAL"; false }; -- V5.3
[txtGeo_Key] = { "FontBody"; "Safe"; false ; "135"; "HORIZONTAL"; false }; -- V5.3
[lblGeoPlot] = { "FontBody"; "Body"; strRight ; "065"; false ; false }; -- V5.3
[lstGeoPlot] = { "FontBody"; "Safe"; false ; "100"; "HORIZONTAL"; false }; -- V5.3
[lblRegBias] = { "FontBody"; "Body"; strRight ; "100"; "HORIZONTAL"; false };
[lstRegBias] = { "FontBody"; "Safe"; false ; "130"; "HORIZONTAL"; false };
[tglGeoCode] = { "FontBody"; "Safe"; strRight ; "080"; false ; false };
[tglLogging] = { "FontBody"; "Safe"; false ; "080"; false ; false }; -- V5.3
[lblLocZoom] = { "FontBody"; "Body"; strRight ; "045"; false ; false };
[txtLocZoom] = { "FontBody"; "Body"; "ARIGHT" ; "022"; false ; false };
[lblFromTag] = { "FontBody"; "Body"; strRight ; "065"; false ; false };
[lstFromTag] = { "FontBody"; "Safe"; false ; "100"; "HORIZONTAL"; false };
[lblStorage] = { "FontBody"; "Body"; strRight ; "100"; "HORIZONTAL"; false };
[lstStorage] = { "FontBody"; "Safe"; false ; "130"; "HORIZONTAL"; false };
[vboxMapsOpt] = { "FontBody"; "Body"; };
[txtMapFolder] = { "FontBody"; "Safe"; false ; "x24"; "HORIZONTAL"; "YES" }; -- V5.3 -- was "x38"
[btnMapFolder] = { "FontBody"; "Safe"; false ;"70x23";false ; false }; -- V5.3
[btnDeleteAll] = { "FontBody"; "Risk"; false ;"70x23";false ; false }; -- V5.3
[lblWebMaps] = { "FontBody"; "Body"; strRight ; "051"; "HORIZONTAL"; false }; -- V6.0 V5.3
[lstWebMaps] = { "FontBody"; "Safe"; false ; "051"; "HORIZONTAL"; false }; -- V6.0 V5.3
[lblPrivacy] = { "FontBody"; "Body"; strRight ; "050"; "HORIZONTAL"; false }; -- V6.0
[lstPrivacy] = { "FontBody"; "Safe"; false ; "051"; "HORIZONTAL"; false }; -- V6.0
[tglFactSet] = { "FontBody"; "Safe"; false ; "045"; false ; false }; -- V6.0 V5.3
[lblMapZoom] = { "FontBody"; "Body"; strRight ; "045"; false ; false }; -- V6.0
[txtMapZoom] = { "FontBody"; "Body"; "ARIGHT" ; "022"; false ; false }; -- V6.0
[lblMarkers] = { "FontBody"; "Body"; strRight ; "051"; "HORIZONTAL"; false }; -- V6.0 V4.3
[lstMarkers] = { "FontBody"; "Safe"; false ; "051"; "HORIZONTAL"; false }; -- V6.0 V4.3
[lblColours] = { "FontBody"; "Body"; strRight ; "050"; "HORIZONTAL"; false }; -- V6.0
[lstColours] = { "FontBody"; "Safe"; false ; "051"; "HORIZONTAL"; false }; -- V6.0
[lblLegends] = { "FontBody"; "Body"; strRight ; "060"; "HORIZONTAL"; false }; -- V6.0 V5.3
[lstLegends] = { "FontBody"; "Safe"; false ; "054"; "HORIZONTAL"; false }; -- V6.0 V5.3
[vboxDataOpt] = { "FontBody"; "Body"; };
[btnCopyPLAC] = { "FontBody"; "Warn"; false ; false; "HORIZONTAL"; false };
[btnCopyADDR] = { "FontBody"; "Warn"; false ; false; "HORIZONTAL"; false };
[btnPurgeBOTH] = { "FontBody"; "Risk"; false ; false; "HORIZONTAL"; false };
[btnErasePLAC] = { "FontBody"; "Risk"; false ; false; "HORIZONTAL"; false };
[btnEraseADDR] = { "FontBody"; "Risk"; false ; false; "HORIZONTAL"; false };
[btnEraseBOTH] = { "FontBody"; "Risk"; false ; false; "HORIZONTAL"; false };
[vboxPrefer] = { "FontBody"; "Body"; "ARIGHT" ; };
[btnDefault] = { "FontBody"; "Safe"; false ; false; "HORIZONTAL"; false };
[btnSetFont] = { "FontBody"; "Safe"; false ; false; "HORIZONTAL"; false };
[tabControls] = { "FontHead"; "Head"; false ; false; false ; "YES" };
[allControls] = { "FontBody"; "Body"; "ARIGHT" ; };
[lblStatus] = { "FontBody"; "Body"; strRight ; "028"; false ; "YES" }; -- Main Dialogue Common controls -- was "36" - V4.2
[LblMessage] = { "FontBody"; "Safe"; "ALEFT" ; false; "HORIZONTAL"; "YES" };
[btnUpdates] = { "FontBody"; "Safe"; false ; "078"; false ; false }; -- V6.0
[btnGetHelp] = { "FontBody"; "Safe"; false ; "078"; false ; false };
[btnDestroy] = { "FontBody"; "Risk"; false ; "078"; false ; false };
[tblTree[IntFromPLAC].Vpane] = { "FontBody"; "Body"; "ACENTER"; }; -- Place/Individual Tree controls
[tblTree[IntFromPLAC].BtnNo] = { "FontBody"; "Risk"; };
[tblTree[IntFromPLAC].BtnEx] = { "FontBody"; "Body"; };
[tblTree[IntFromPLAC].BtnOk] = { "FontBody"; "Safe"; };
[tblTree[IntFromADDR].Vpane] = { "FontBody"; "Body"; "ACENTER"; };
[tblTree[IntFromADDR].BtnNo] = { "FontBody"; "Risk"; };
[tblTree[IntFromADDR].BtnEx] = { "FontBody"; "Body"; };
[tblTree[IntFromADDR].BtnOk] = { "FontBody"; "Safe"; };
[tblTree[IntFromBOTH].Vpane] = { "FontBody"; "Body"; "ACENTER"; };
[tblTree[IntFromBOTH].BtnNo] = { "FontBody"; "Risk"; };
[tblTree[IntFromBOTH].BtnEx] = { "FontBody"; "Body"; };
[tblTree[IntFromBOTH].BtnOk] = { "FontBody"; "Safe"; };
[tblTree[IntFromINDI].Vpane] = { "FontBody"; "Body"; "ACENTER"; };
[tblTree[IntFromINDI].BtnNo] = { "FontBody"; "Risk"; };
[tblTree[IntFromINDI].BtnEx] = { "FontBody"; "Body"; };
[tblTree[IntFromINDI].BtnOk] = { "FontBody"; "Safe"; };
}
iup_gui.AssignAttributes(tblControls) -- Assign control attributes
if IsGoogleKeyMissing() -- Startup splash Geocoder Help and Warning Message for user without API Key -- V4.2 -- V4.3 -- V5.3
and IsGeo_KeyMissing()
and IsSplashOn == "API" then
local strMessageText = "\nEvery user of this Plugin should obtain at least one personal 'API Key'\nto enable the 'Google Maps' or 'Geoapify' services.\n\nSee the 'Help && Advice' for 'Geocoding and Mapping' features and options.\n\nOtherwise, disable 'Google Maps' && 'Geoapify' and use 'Leaflet Maps' only.\n\n"
repeat
local intAnswer =
iup_gui.WarnDialogue( -- V4.2
{ "API Key Required" ; iup_gui.Warn; },
{ strMessageText ; iup_gui.Info; "API Key Required"; },
{ " Help && Advice " ; iup_gui.Safe; "Show the Help and Advice page"; },
{ " Set Google API Key " ; iup_gui.Safe; "Enter your personal 'API Key' for 'Google Maps'"; }, -- V5.3
{ "Set Geoapify API Key" ; iup_gui.Safe; "Enter your personal 'API Key' for 'Geoapify'"; }, -- V5.3
{ " Use Leaflet Maps " ; iup_gui.Safe; "Disable 'Google & Geoapify API' and use 'Leaflet Maps'"; }, -- V5.3 -- V6.0
{ " Close the Plugin " ; iup_gui.Risk; "Close the Plugin immediately"; }
)
if intAnswer == 0 or intAnswer == 6 then return end -- Close the Plugin -- V4.2
if intAnswer == 1 then doGetHelp(4) end -- Show Help & Advice -- V4.2 -- V4.8
until intAnswer >= 2 and intAnswer <= 5 -- V5.3
IntWebMaps = DicMapMode.Leaflet -- Enable Leaflet Maps -- V4.3
setMapMode(IntWebMaps)
IntGeoPlot = DicGeoPlot.Disabled -- Geocoder Disabled -- V5.3
lstGeoPlot.Value = IntGeoPlot + 1
IntTabPosn = IntTabPrefer
tabControls.ValuePos = IntTabPosn - 1 -- Set tab and Help for Set Preference Options to enter API Key -- V4.2
doGetHelp(3) -- V4.8
end
iup_gui.ShowDialogue("Main") -- Now display resizable dialogue
end -- function GUI_MainDialogue
IntFhVersion = fhGetAppVersion()
function ConvertANSItoUTF8(strText)
if IntFhVersion > 5 then -- Fix for UTF8 characters -- V4.1
return fhConvertANSItoUTF8(strText)
end
return strText
end -- function ConvertANSItoUTF8(strText)
TblRegBias = { -- Geocoder Region Bias Top Level Domain Codes
" None";
"ac Ascension Island";
"ad Andorra";
"ae United Arab Emirates";
"af Afghanistan";
"ag Antigua & Barbuda";
"ai Anguilla";
"al Albania";
"am Armenia";
"an Netherlands Antilles";
"ao Angola";
"aq Antarctica";
"ar Argentina";
"as American Samoa";
"at Austria";
"au Australia";
"aw Aruba";
ConvertANSItoUTF8("ax Åland");
"az Azerbaijan";
"ba Bosnia & Herzegovina";
"bb Barbados";
"bd Bangladesh";
"be Belgium";
"bf Burkina Faso";
"bg Bulgaria";
"bh Bahrain";
"bi Burundi";
"bj Benin";
"bm Bermuda";
"bn Brunei";
"bo Bolivia";
"br Brazil";
"bs Bahamas";
"bt Bhutan";
"bv Bouvet Island";
"bw Botswana";
"by Belarus";
"bz Belize";
"ca Canada";
"cc Cocos (Keeling) Islands";
"cd Democratic Republic of the Congo";
"cf Central African Republic";
"cg Republic of the Congo";
"ch Switzerland";
ConvertANSItoUTF8("ci Côte d'Ivoire");
"ck Cook Islands";
"cl Chile";
"cm Cameroon";
"cn People's Republic of China";
"co Colombia";
"cr Costa Rica";
"cs Czechoslovakia";
"cu Cuba";
"cv Cape Verde";
"cx Christmas Island";
"cy Cyprus";
"cz Czech Republic";
"dd East Germany";
"de Germany";
"dj Djibouti";
"dk Denmark";
"dm Dominica";
"do Dominican Republic";
"dz Algeria";
"ec Ecuador";
"ee Estonia";
"eg Egypt";
"eh Western Sahara";
"er Eritrea";
"es Spain";
"et Ethiopia";
"eu European Union";
"fi Finland";
"fj Fiji";
"fk Falkland Islands";
"fm Federated States of Micronesia";
"fo Faroe Islands";
"fr France";
"ga Gabon";
"gb Great Britain";
"gd Grenada";
"ge Georgia";
"gf French Guiana";
"gg Guernsey";
"gh Ghana";
"gi Gibraltar";
"gl Greenland";
"gm The Gambia";
"gn Guinea";
"gp Guadeloupe";
"gq Equatorial Guinea";
"gr Greece";
"gs South Georgia & South Sandwich Islands";
"gt Guatemala";
"gu Guam";
"gw Guinea-Bissau";
"gy Guyana";
"hk Hong Kong";
"hm Heard Island & McDonald Islands";
"hn Honduras";
"hr Croatia";
"ht Haiti";
"hu Hungary";
"id Indonesia";
"ie Republic of Ireland & Northern Ireland";
"il Israel";
"im Isle of Man";
"in India";
"io British Indian Ocean Territory";
"iq Iraq";
"ir Iran";
"is Iceland";
"it Italy";
"je Jersey";
"jm Jamaica";
"jo Jordan";
"jp Japan";
"ke Kenya";
"kg Kyrgyzstan";
"kh Cambodia";
"ki Kiribati";
"km Comoros";
"kn Saint Kitts and Nevis";
"kp Democratic People's Republic of Korea";
"kr Republic of Korea";
"kw Kuwait";
"ky Cayman Islands";
"kz Kazakhstan";
"la Laos";
"lb Lebanon";
"lc Saint Lucia";
"li Liechtenstein";
"lk Sri Lanka";
"lr Liberia";
"ls Lesotho";
"lt Lithuania";
"lu Luxembourg";
"lv Latvia";
"ly Libya";
"ma Morocco";
"mc Monaco";
"md Moldova";
"me Montenegro";
"mg Madagascar";
"mh Marshall Islands";
"mk Republic of Macedonia";
"ml Mali";
"mm Myanmar";
"mn Mongolia";
"mo Macau";
"mp Northern Mariana Islands";
"mq Martinique";
"mr Mauritania";
"ms Montserrat";
"mt Malta";
"mu Mauritius";
"mv Maldives";
"mw Malawi";
"mx Mexico";
"my Malaysia";
"mz Mozambique";
"na Namibia";
"nc New Caledonia";
"ne Niger";
"nf Norfolk Island";
"ng Nigeria";
"ni Nicaragua";
"nl Netherlands";
"no Norway";
"np Nepal";
"nr Nauru";
"nu Niue";
"nz New Zealand";
"om Oman";
"pa Panama";
"pe Peru";
"pf French Polynesia";
"pg Papua New Guinea";
"ph Philippines";
"pk Pakistan";
"pl Poland";
"pm Saint-Pierre & Miquelon";
"pn Pitcairn Islands";
"pr Puerto Rico";
"ps Palestinian Territories";
"pt Portugal";
"pw Palau";
"py Paraguay";
"qa Qatar";
ConvertANSItoUTF8("re Réunion");
"ro Romania";
"rs Serbia";
"ru Russia";
"rw Rwanda";
"sa Saudi Arabia";
"sb Solomon Islands";
"sc Seychelles";
"sd Sudan";
"se Sweden";
"sg Singapore";
"sh Saint Helena";
"si Slovenia";
"sj Svalbard & Jan Mayen Islands";
"sk Slovakia";
"sl Sierra Leone";
"sm San Marino";
"sn Senegal";
"so Somalia";
"sr Suriname";
"ss South Sudan";
ConvertANSItoUTF8("st São Tomé & Príncipe");
"su Soviet Union";
"sv El Salvador";
"sy Syria";
"sz Swaziland";
"tc Turks & Caicos Islands";
"td Chad";
"tf French Southern & Antarctic Lands";
"tg Togo";
"th Thailand";
"tj Tajikistan";
"tk Tokelau";
"tl East Timor";
"tm Turkmenistan";
"tn Tunisia";
"to Tonga";
"tp East Timor";
"tr Turkey";
"tt Trinidad & Tobago";
"tv Tuvalu";
"tw Republic of China (Taiwan)";
"tz Tanzania";
"ua Ukraine";
"ug Uganda";
"uk United Kingdom";
"us United States of America";
"uy Uruguay";
"uz Uzbekistan";
"va Vatican City";
"vc Saint Vincent & Grenadines";
"ve Venezuela";
"vg British Virgin Islands";
"vi United States Virgin Islands";
"vn Vietnam";
"vu Vanuatu";
"wf Wallis & Futuna";
"ws Samoa";
"ye Yemen";
"yt Mayotte";
"yu Yugoslavia";
"za South Africa";
"zm Zambia";
"zw Zimbabwe";
}
TblNation = { -- Nation recognition to resolve smart code conflicts -- V5.0
["canada"] = "America"; -- America invokes TblLocCode.America below
["ca"] = "America";
["united states"] = "America";
["us"] = "America";
["australia"] = "Austral"; -- Australia invokes TblLocCode.Austral below
["au"] = "Austral";
["united kingdom"] = "British"; -- British invokes TblLocCode.British below
["uk"] = "British";
["britain"] = "British";
["england"] = "British";
["ireland"] = "British";
["isle of man"] = "British";
["scotland"] = "British";
["wales"] = "British";
["channel islands"] = "British";
["alderney"] = "British";
["guernsey"] = "British";
["jersey"] = "British";
["sark"] = "British";
}
TblLocCode = { -- Geocoder Location Code Translations (For conflicts, see later national resolution tables)
Default = {
ABW="Aruba"; -- Country Codes from http://en.wikipedia.org/wiki/ISO_3166-1_alpha-3 (ISO 3166-1 alpha-3 codes)
AFG="Afghanistan";
AGO="Angola";
AIA="Anguilla";
ALA=ConvertANSItoUTF8("Åland Islands");
ALB="Albania";
AND="Andorra";
ARE="United Arab Emirates";
ARG="Argentina";
ARM="Armenia"; -- Conflict with ARM="Armagh" Chapman Code
ASC="Ascension Island"; -- Exceptional Reservation
ASM="American Samoa";
ATA="Antarctica";
ATF="French Southern Territories";
ATG="Antigua and Barbuda";
AUS="Australia";
AUT="Austria";
AZE="Azerbaijan";
BDI="Burundi";
BEL="Belgium";
BEN="Benin";
BES="Bonaire, Sint Eustatius and Saba";
BFA="Burkina Faso";
BGD="Bangladesh";
BGR="Bulgaria";
BHR="Bahrain";
BHS="Bahamas";
BIH="Bosnia and Herzegovina";
BLM=ConvertANSItoUTF8("Saint Barthélemy");
BLR="Belarus";
BLZ="Belize";
BMU="Bermuda";
BOL="Bolivia"; -- Plurinational State of
BRA="Brazil";
BRB="Barbados";
BRN="Brunei Darussalam";
BTN="Bhutan";
BVT="Bouvet Island";
BWA="Botswana";
CAF="Central African Republic";
CAN="Canada";
CCK="Cocos (Keeling) Islands";
CHE="Switzerland";
CHL="Chile";
CHN="China";
CIV=ConvertANSItoUTF8("Côte d'Ivoire");
CMR="Cameroon";
COD="Democratic Republic of the Congo";
COG="Congo";
COK="Cook Islands";
COL="Colombia";
COM="Comoros";
CPT="Clipperton Island"; -- Exceptional Reservation
CPV="Cape Verde";
CRI="Costa Rica";
CUB="Cuba";
CUW=ConvertANSItoUTF8("Curaçao");
CXR="Christmas Island";
CYM="Cayman Islands";
CYP="Cyprus";
CZE="Czech Republic";
DEU="Germany";
DGA="Diego Garcia"; -- Exceptional Reservation
DJI="Djibouti";
DMA="Dominica";
DNK="Denmark";
DOM="Dominican Republic";
DZA="Algeria";
ECU="Ecuador";
EGY="Egypt";
ERI="Eritrea";
ESH="Western Sahara";
ESP="Spain";
EST="Estonia";
ETH="Ethiopia";
FIN="Finland";
FJI="Fiji";
FLK="Falkland Islands (Malvinas)";
FRA="France";
FRO="Faroe Islands";
FSM="Micronesia"; -- Federated States of
FXX="France"; -- Metropolitan -- Exceptional Reservation
GAB="Gabon";
GBR="United Kingdom";
GEO="Georgia (country)";
GGY="Guernsey";
GHA="Ghana";
GIB="Gibraltar";
GIN="Guinea";
GLP="Guadeloupe";
GMB="Gambia";
GNB="Guinea-Bissau";
GNQ="Equatorial Guinea";
GRC="Greece";
GRD="Grenada";
GRL="Greenland";
GTM="Guatemala";
GUF="French Guiana";
GUM="Guam";
GUY="Guyana";
HKG="Hong Kong";
HMD="Heard Island and McDonald Islands";
HND="Honduras";
HRV="Croatia";
HTI="Haiti";
HUN="Hungary"; -- Conflict with HUN="Huntingdonshire" Chapman Code
IDN="Indonesia";
IMN="Isle of Man";
IND="India";
IOT="British Indian Ocean Territory";
IRL="Ireland"; -- Equals Chapman Country Code
IRN="Iran"; -- Islamic Republic of
IRQ="Iraq";
ISL="Iceland";
ISR="Israel";
ITA="Italy";
JAM="Jamaica";
JEY="Jersey";
JOR="Jordan";
JPN="Japan";
KAZ="Kazakhstan";
KEN="Kenya"; -- Conflict with KEN="Kent" Chapman Code
KGZ="Kyrgyzstan";
KHM="Cambodia";
KIR="Kiribati";
KNA="Saint Kitts and Nevis";
KOR="South Korea"; -- Republic of Korea
KWT="Kuwait";
LAO="Laos"; -- Lao People's Democratic Republic
LBN="Lebanon";
LBR="Liberia";
LBY="Libya";
LCA="Saint Lucia";
LIE="Liechtenstein";
LKA="Sri Lanka";
LSO="Lesotho";
LTU="Lithuania";
LUX="Luxembourg";
LVA="Latvia";
MAC="Macao";
MAF="Saint Martin"; -- (French part)
MAR="Morocco";
MCO="Monaco";
MDA="Moldova"; -- Republic of
MDG="Madagascar";
MDV="Maldives";
MEX="Mexico";
MHL="Marshall Islands";
MKD="Macedonia"; -- the former Yugoslav Republic of
MLI="Mali";
MLT="Malta";
MMR="Myanmar";
MNE="Montenegro";
MNG="Mongolia";
MNP="Northern Mariana Islands";
MOZ="Mozambique";
MRT="Mauritania";
MSR="Montserrat";
MTQ="Martinique";
MUS="Mauritius";
MWI="Malawi";
MYS="Malaysia";
MYT="Mayotte";
NAM="Namibia";
NCL="New Caledonia";
NER="Niger";
NFK="Norfolk Island"; -- Conflict with NFK="Norfolk" Chapman Code
NGA="Nigeria";
NIC="Nicaragua";
NIU="Niue";
NLD="Netherlands";
NOR="Norway";
NPL="Nepal";
NRU="Nauru";
NZL="New Zealand";
OMN="Oman";
PAK="Pakistan";
PAN="Panama";
PCN="Pitcairn";
PER="Peru"; -- Conflict with PER="Perthshire" Chapman Code
PHL="Philippines";
PLW="Palau";
PNG="Papua New Guinea";
POL="Poland";
PRI="Puerto Rico";
PRK="North Korea"; -- Democratic People's Republic of Korea
PRT="Portugal";
PRY="Paraguay";
PSE="Palestine"; -- Palestinian Territory, Occupied
PYF="French Polynesia";
QAT="Qatar";
REU=ConvertANSItoUTF8("Réunion");
ROU="Romania";
RUS="Russian Federation";
RWA="Rwanda";
SAU="Saudi Arabia";
SDN="Sudan";
SEN="Senegal";
SGP="Singapore";
SGS="South Georgia and the South Sandwich Islands";
SHN="Ascension, Saint Helena";-- Saint Helena, Ascension and Tristan da Cunha see TAA
SJM="Svalbard and Jan Mayen";
SLB="Solomon Islands";
SLE="Sierra Leone";
SLV="El Salvador";
SMR="San Marino";
SOM="Somalia"; -- Conflict with SOM="Somerset" Chapman Code
SPM="Saint Pierre and Miquelon";
SRB="Serbia";
SSD="South Sudan";
STP="Sao Tome and Principe";
SUN="USSR Russia"; -- Exceptional Reservation
SUR="Suriname";
SVK="Slovakia";
SVN="Slovenia";
SWE="Sweden";
SWZ="Swaziland";
SXM="Sint Maarten"; -- (Dutch part)
SYC="Seychelles";
SYR="Syrian Arab Republic";
TAA="Tristan da Cunha"; -- Exceptional Reservation see SHN
TCA="Turks and Caicos Islands";
TCD="Chad";
TGO="Togo";
THA="Thailand";
TJK="Tajikistan";
TKL="Tokelau";
TKM="Turkmenistan";
TLS="Timor-Leste";
TON="Tonga";
TTO="Trinidad and Tobago";
TUN="Tunisia";
TUR="Turkey";
TUV="Tuvalu";
TWN="Taiwan"; -- Province of China
TZA="Tanzania"; -- United Republic of
UGA="Uganda";
UKR="Ukraine";
UMI="United States Minor Outlying Islands";
URY="Uruguay";
USA="United States";
UZB="Uzbekistan";
VAT="Vatican City"; -- Holy See (Vatican City State)
VCT="Saint Vincent and the Grenadines";
VEN="Venezuela"; -- Bolivarian Republic of
VGB="British Virgin Islands";
VIR="U.S. Virgin Islands";
VNM="Viet Nam";
VUT="Vanuatu";
WLF="Wallis and Futuna";
WSM="Samoa";
YEM="Yemen";
ZAF="South Africa";
ZMB="Zambia";
ZWE="Zimbabwe";
-- United States Postal Service State Abbreviations from http://en.wikipedia.org/wiki/List_of_U.S._state_abbreviations
AL="Alabama";
AK="Alaska";
AZ="Arizona";
AR="Arkansas";
CA="California";
CO="Colorado";
CT="Connecticut";
DE="Delaware";
DC="District of Columbia";
FL="Florida";
GA="Georgia";
HI="Hawaii";
ID="Idaho";
IL="Illinois";
IN="Indiana";
IA="Iowa";
KS="Kansas";
KY="Kentucky";
LA="Louisiana";
ME="Maine";
MD="Maryland";
MA="Massachusetts";
MI="Michigan";
MN="Minnesota";
MS="Mississippi";
MO="Missouri";
MT="Montana";
NE="Nebraska";
NV="Nevada";
NH="New Hampshire";
NJ="New Jersey";
NM="New Mexico";
NY="New York State";
NC="North Carolina";
ND="North Dakota";
OH="Ohio";
OK="Oklahoma";
OR="Oregon";
PA="Pennsylvania";
RI="Rhode Island";
SC="South Carolina";
SD="South Dakota";
TN="Tennessee";
TX="Texas";
UT="Utah";
VT="Vermont";
VA="Virginia";
WA="Washington State"; -- Conflict with WA="Western Australia" Australian State Abbreviation
WV="West Virginia";
WI="Wisconsin";
WY="Wyoming";
AS="American Samoa"; -- Insular Areas:
GU="Guam";
MP="Northern Mariana Islands";
PR="Puerto Rico";
VI="Virgin Islands";
UM="United States Minor Outlying Islands";
FM="Federated States of Micronesia"; -- Freely Associated States:
MH="Marshall Islands";
PW="Palau";
--! AA="Armed Forces - Americas (except Canada)"; -- US Military Mail:
--! AE="Armed Forces - Europe, Canada, Middle East, Africa";
--! AP="Armed Forces - Pacific";
CZ="Panama Canal"; -- "Canal Zone" -- Obsolete Codes:
PI="Philippines Islands";
TT="Pacific Islands"; -- Trust Territory of the
CM="Commonwealth of the Northern Mariana Islands";
-- Talk:List of U.S. State Abbreviations http://en.wikipedia.org/wiki/Talk:List_of_U.S._state_abbreviations
--! IA="Indiana"; -- Conflict with IA="Iowa" above
WN="Washington State";
-- Canadian Province Abbreviations from http://canadaonline.about.com/od/postalservices/a/abbreviations-provinces-canada.htm?rd=1
AB="Alberta";
BC="British Columbia";
MB="Manitoba";
NB="New Brunswick, Canada";
NL="Newfoundland and Labrador";
NT="Northwest Territories"; -- Conflict with NT="Northern Territory" Australian State
NS="Nova Scotia";
NU="Nunavut";
ON="Ontario";
PE="Prince Edward Island";
QC="Quebec";
SK="Saskatchewan";
YT="Yukon";
-- Chapman Codes from http://en.wikipedia.org/wiki/Chapman_code (superset of ISO 3166-2:GB & BS 6879)
CHI="Channel Islands"; -- Country Codes
ENG="England";
IOM="Isle of Man";
IRL="Ireland"; -- Equals Country Code ISO_3166
SCT="Scotland";
WLS="Wales";
ALL=""; -- All countries
ALD="Alderney"; -- Channel Islands
GSY="Guernsey";
JSY="Jersey";
SRK="Sark";
};
America = { -- American specific codes
NT="Northwest Territories"; -- Conflict with NT="Northern Territory" Australian State
WA="Washington State"; -- Conflict with WA="Western Australia" Australian State Abbreviation
};
Austral = { -- Australian State Abbreviations from http://en.wikipedia.org/wiki/States_and_territories_of_Australia (Postal codes) -- V5.0
ACT="Australian Capital Territory";
JBT="Jervis Bay Territory";
NSW="New South Wales";
NT ="Northern Territory"; -- Conflict with NT="Northwest Territories" Canadian Province
QLD="Queensland";
SA ="South Australia";
TAS="Tasmania";
VIC="Victoria, Australia"; -- Otherwise "Victoria, British Columbia" in Canada is chosen
WA ="Western Australia"; -- Conflict with WA="Washington State" US Postal Service State Abbreviation
};
British = { -- British Chapman Codes & Postal County Abbreviations -- V5.0
BDF="Bedfordshire, UK"; -- England Ancient Counties
BRK="Berkshire, UK";
BKM="Buckinghamshire, UK";
CAM="Cambridgeshire, UK";
CHS="Cheshire County, UK";
CON="Cornwall, UK";
CUL="Cumbria, UK"; -- "Cumberland" not recognised by Geocoder
DBY="Derbyshire, UK";
DEV="Devonshire, UK";
DOR="Dorset, UK";
DUR="County Durham, UK";
ESS="Essex, UK";
GLS="Gloucestershire, UK";
HAM="Hampshire, UK";
HEF="Herefordshire, UK";
HRT="Hertfordshire, UK";
HUN="Huntingdonshire, UK"; -- Conflict with HUN="Hungary" Country Code
IOW="Isle of Wight, UK";
KEN="Kent, UK"; -- Conflict with KEN="Kenya" Country Code
LAN="Lancashire, UK";
LEI="Leicestershire, UK";
LIN="Lincolnshire, UK";
LND="London, UK"; -- (City only)
MDX="London, UK"; -- "Middlesex" not recognised by Geocoder
NFK="Norfolk, UK"; -- Conflict with NFK="Norfolk Island" Country Code
NTH="Northamptonshire, UK";
NBL="Northumberland, UK";
NTT="Nottinghamshire, UK";
OXF="Oxfordshire, UK";
RUT="Rutland, UK";
SAL="Shropshire, UK";
SOM="Somerset, UK"; -- Conflict with SOM="Somalia" Country Code
STS="Staffordshire, UK";
SFK="Suffolk, UK";
SRY="Surrey, UK";
SSX="Sussex, UK";
WAR="Warwickshire, UK";
WES="Cumbria, UK"; -- "Westmorland" not recognised by Geocoder
WIL="Wiltshire, UK";
WOR="Worcestershire, UK";
YKS="Yorkshire, England";
ERY="Yorkshire, England"; -- East Riding
NRY="Yorkshire, England"; -- North Riding
WRY="Yorkshire, England"; -- West Riding
ABD="Aberdeenshire, UK"; -- Scotland Ancient Counties
ANS="Angus, UK"; -- (formerly Forfarshire)
ARL="Argyll County, UK"; -- "Argyllshire" not recognised by Geocoder
AYR="Ayrshire, UK";
BAN="Moray, UK"; -- "Banffshire" not recognised by Geocoder
BEW="Berwickshire, UK";
BUT="Bute, UK"; -- (Buteshire)
CAI="Caithness, UK";
CLK="Clackmannanshire, UK";
DFS="Dumfriesshire, UK";
DNB="Dunbartonshire, UK";
ELN="East Lothian, UK"; -- (formerly Haddingtonshire)
FIF="Fife, UK";
INV="Inverness,Highland,UK"; -- "Inverness-shire" not recognised by Geocoder with Fort Augustus
KCD="Aberdeenshire, UK"; -- "Kincardineshire" not recognised by Geocoder
KRS="Kinrossshire, UK";
KKD="Kirkcudbright, UK"; -- "Kirkcudbrightshire"
LKS="Lanarkshire, UK";
MLN="Midlothian, UK"; -- (formerly Edinburghshire)
MOR="Moray, UK"; -- (formerly Elginshire)
NAI="Nairnshire, UK";
OKI="Orkney Isles, UK";
PEE="Peebles, UK";
PER="Perthshire, UK"; -- Conflict with PER="Peru" Country Code
RFW="Renfrewshire, UK";
ROC="Ross, Highland, UK"; -- "Ross and Cromarty" not recognised by Geocoder
ROX="Scottish Borders, UK"; -- "Roxburghshire"
SEL="Selkirkshire, UK";
SHI="Shetland Isles, UK";
STI="Stirlingshire, UK";
SUT="Highland, UK"; -- "Sutherland" not recognised by Geocoder
WLN="West Lothian, UK"; -- (formerly Linlithgowshire)
WIG="Galloway, Scotland,UK"; -- "Wigtownshire" not recognised by Geocoder
BOR="Borders, Scotland, UK"; -- Scotland 1975-1996 Regions
CEN="Central Region, Scotland, UK";
DGY="Dumfries and Galloway, UK";
FIF="Fife, UK"; -- Duplicates Ancient County
GMP="Aberdeenshire, UK"; -- "Grampian" not recognised by Geocoder
HLD="Highland, UK";
LTN="Lothian, UK";
OKI="Orkney Isles, UK"; -- Duplicates Ancient County
SHI="Shetland Isles, UK"; -- Duplicates Ancient County
STD="Scotland, UK"; -- "Strathclyde" not recognised by Geocoder
TAY="Perthshire, UK"; -- "Tayside" not recognised by Geocoder
WIS="Outer Hebrides, UK"; -- "Western Isles" not recognised by Geocoder
AGY="Anglesey, UK"; -- Wales Ancient Counties
BRE="Breconshire, UK"; -- "Brecknock" & "Brecknockshire" not recognised by Geocoder
CAE="Caernarvon, Wales, UK"; -- "Caernarfonshire" not recognised by Geocoder
CGN="Cardiganshire, UK";
CMN="Carmarthenshire, UK";
DEN="Denbighshire, UK";
FLN="Flintshire, UK";
GLA="Glamorgan, UK";
MER="Merionethshire, UK";
MON="Monmouthshire, UK";
MGY="Montgomeryshire, UK";
PEM="Pembrokeshire, UK";
RAD="Radnorshire, UK";
CWD="Clwyd, Wales, UK"; -- Wales 1974-1996
DFD="Dyfed, Wales, UK";
GNT="Gwent, Wales, UK";
GWN="Gwynedd, Wales, UK";
MGM="Mid Glamorgan, Wales, UK";
POW="Powys, Wales, UK";
SGM="South Glamorgan, Wales, UK";
WGM="Glamorgan, Wales, UK"; -- "West Glamorgan" not recognised by Geocoder
ANT="Antrim County, UK"; -- Ireland
ARM="Armagh County, UK"; -- Conflict with ARM="Armenia" Country Code
CAR="Carlow County, Ireland";
CAV="Cavan County, Ireland";
CLA="Clare County, Ireland";
COR="Cork County, Ireland";
DON="Donegal County, Ireland";
DOW="Down County, UK";
DUB="Dublin County, Ireland";
FER="Fermanagh County, UK";
GAL="Galway County, Ireland";
KER="Kerry County, Ireland";
KID="Kildare County, Ireland";
KIK="Kilkenny County, Ireland";
LET="Leitrim County, Ireland";
LEX="Laois County, Ireland"; -- "Leix" (formerly Queen's)
LIM="Limerick County, Ireland";
LDY="Londonderry County, UK";
LOG="Longford County, Ireland";
LOU="Louth County, Ireland";
MAY="Mayo County, Ireland";
MEA="Meath County, Ireland";
MOG="Monaghan County, Ireland";
OFF="Offaly County, Ireland"; -- (formerly King's)
ROS="Roscommon County, Ireland";
SLI="Sligo County, Ireland";
TIP="Tipperary County, Ireland";
TYR="Tyrone County, UK";
WAT="Waterford County, Ireland";
WEM="Westmeath County, Ireland";
WEX="Wexford County, Ireland";
WIC="Wicklow County, Ireland";
-- UK Postal County Abbreviations subset from http://en.wikipedia.org/wiki/Postal_counties_of_the_United_Kingdom#Postal_counties_in_operation_from_1974_to_1996
BEDS ="Bedfordshire, UK";
BERKS ="Berkshire, UK";
BUCKS ="Buckinghamshire, UK";
CAMBS ="Cambridgeshire, UK";
GLOS ="Gloucestershire, UK";
HANTS ="Hampshire, UK";
HERTS ="Hertfordshire, UK";
HUMBS ="Humberside, UK"; -- N Humbs & S Humbs
LANCS ="Lancashire, UK";
LEICS ="Leicestershire, UK";
LINCS ="Lincolnshire, UK";
MIDDX ="London, UK";
NORTHANTS ="Northamptonshire, UK";
NORTHD ="Northumberland, UK";
NOTTS ="Nottinghamshire, UK";
OXON ="Oxfordshire, UK";
SALOP ="Shropshire, UK";
STAFFS ="Staffordshire, UK";
WARKS ="Warwickshire, UK";
WILTS ="Wiltshire, UK";
WORCS ="Worcestershire, UK";
YORKS ="Yorkshire, England"; -- N Yorks & S Yorks & W Yorks
GLAM ="Glamorgan, Wales, UK"; -- M Glam & S Glam & W Glam
};
Other = { }; -- Cater for any other unknown Nation
}
TblGeoCode = { -- Geocoder special translations to improve plotting
-- UK County Substitutions that tend to work better with Google Geocoder
{ "^Ecclesall Bierlow" ; "Ecclesall"; };
{ "^East%s.*Yorkshire" ; "Yorkshire, England"; };
{ "^North%s.*Yorkshire" ; "Yorkshire, England"; };
{ "^South%s.*Yorkshire" ; "Yorkshire, England"; };
{ "^West%s.*Yorkshire" ; "Yorkshire, England"; };
{ "^Yorkshire.*East%s.*" ; "Yorkshire, England"; };
{ "^Yorkshire.*North%s.*" ; "Yorkshire, England"; };
{ "^Yorkshire.*South%s.*" ; "Yorkshire, England"; };
{ "^Yorkshire.*West%s.*" ; "Yorkshire, England"; };
}
-- Global Script Definitions -- -- V5.1 -- Hex encoded HTM, CSS, JS & HTML files
-- HTM script template for managing the Plugin map pane
StrMasterHTM = [=[
<!DOCTYPE html>
<html>
<head>
<title>{plugin}</title>
<meta name='viewport' content='initial-scale=1.0,user-scalable=no'>
<meta http-equiv='x-ua-compatible' content='IE=edge'>
<meta http-equiv='content-type' content='text/html; charset=utf-8'>
<link type='text/css' rel='stylesheet' href='mapfacts.css'>
<script type='text/javascript' src='mapfacts.js'> </script>
<script type='text/javascript' language='JavaScript'>
function Initialise() // Initialisation function -- V4.5 invoked by &callback or window.onload in StrMasterJS shared JavaScript
{
MapMark = new Array(); // Map Marker overlays
LatLong = new Array(); // Map LatLng positions
LocName = new Array(); // InfoWindow location place name -- V4.2
Caption = new Array(); // Marker caption title tooltips
InfoWin = new Array(); // Marker popup InfoWindow facts for Google & Leaflet Maps
CheckOnline( // Check if PC is online
function(){ OfflineError('map'); },
function(){ OnlineAction(); }
);
}
function OnlineAction()
{
SynOld = ''; // Synchronisation old char
SynNew = ''; // Synchronisation new char
Action = ''; // Action command code e.g. 'MARK', 'MOVE', 'NULL', 'PLOT', 'DROP', 'ZOOM'
Value1 = ''; // Latitude or Drop animation or Zoom level
Value2 = ''; // Longitude or Drop delay
Value3 = ''; // Location Address/Place
Value4 = ''; // Caption Address/Place -- V4.2
Value5 = ''; // Recorded Facts text
BanZoom = 1; // Banned map Zoom level to exclude from reports
SetZoom = 1; // Current map Zoom level
SetZoomLevel({setzoom}); // Integer between 0 and 22
MarkZoom = SetZoom; // Default zoom level for single 'MARK' Markers
Animation = null; // Marker animation mode
SetAnimation('{animate}'); // 'null'=No Animation, or 'DROP'=Synchro/Cascade
SetPause = 0; // Pause for 0ms before dropping Markers
SetDelay = 0; // Marker delay of 0ms=Immediate, ~300ms=No Animation/Synchro, or ~2000ms=Cascade
SetBound = null; // Bounds offset to fit Markers well within Map -- V4.3
SetWait = 250; // Wait between checks for new action file
Greenwich = NewLatLong(51.5,0.0); // Greenwich = (51.5,0.0)
Home = NewLatLong(5.65,0.0); // Tema, Accra, Ghana is nearest urban area to (0.0,0.0) N.B. Greenwich = (51.5,0.0)
MakeMapPane('Plugin','{basemap}'); // Make the map pane display for 'Plugin' mode and Leaflet Base Map Layer
xmlDoc = new ActiveXObject('Microsoft.XMLDOM'); // or ActiveXObject('msxml2.DOMDocument.6.0');
xmlDoc.async=false; // See http://msdn.microsoft.com/en-us/library/windows/desktop/ms766487%28v=VS.85%29.aspx
LoadFile('{plugin}.act');
}
function LoadFile(filename)
{
var status = xmlDoc.load(filename); // Load the input action file
if ( status )
{
if ( xmlDoc.parseError.errorCode == 0 )
{
var line = xmlDoc.xml.split('\n'); // Split action file into lines & remove carriage returns
SynNew = line[1].replace('\r',''); // Synchronisation char
Action = line[2].replace('\r',''); // Action command code
Value1 = line[3].replace('\r',''); // Latitude or Drop delay or Zoom level
Value2 = line[4].replace('\r',''); // Longitude or Drop animation style
Value3 = line[5].replace('\r',''); // Location Address/Place or Bounds offset
Value4 = line[6].replace('\r',''); // Caption Address/Place -- V4.2
Value5 = line[7].replace('\r',''); // Recorded Facts text
if ( SynOld != SynNew )
{
SynOld = SynNew;
DoAction(); // After synchronisation changes, perform the action
}
else // Wait for new action file to appear
{
setTimeout(function(){LoadFile(filename);},SetWait);
}
}
else
{
var myErr = xmlDoc.parseError; // Input action file has errors
SendStatus('You have error ' + myErr.reason);
}
}
else
{
setTimeout(function(){LoadFile(filename);},SetWait);// Wait for new action file to appear
}
}
function DoAction() // Perform each action command
{
switch (Action)
{
case 'MARK': // MARK the Geocoded location
RemoveMarkers();
if ( isNaN(parseFloat(Value1)) || isNaN(parseFloat(Value2)) )
{
BanZoom = 1; // Lat/Long missing so Zoom out and pan to Home to show whole world
PanTo(Home,1);
}
else
{ // Mark supplied Latitude & Longitude with popup InfoWindow and tooltip Caption
LatLong[0] = NewLatLong(Value1,Value2);
LocName[0] = Value3; // -- V4.2
Caption[0] = Value4; // -- V4.2
InfoWin[0] = 'Recorded Fact(s) at';
SetDelay = 0;
MakeMarkers();
PanTo(LatLong[0],MarkZoom); // Zoom in and pan to Marker
DropMarker(0);
BanZoom = 0;
}
SetWait = 250; // Increase wait between checks for new action file
break;
case 'MOVE': // MOVE existing Marker without animation or zoom
if ( isNaN(parseFloat(Value1)) || isNaN(parseFloat(Value2)) )
{
BanZoom = 1; // Lat/Long missing so Zoom out and pan to Home to show whole world
PanTo(Home,1);
}
else
{
LatLong[0] = NewLatLong(Value1,Value2); // Move to supplied Latitude & Longitude
MoveMarker(0);
}
SetWait = 250; // Increase wait between checks for new action file
break;
case 'NULL': // NULLify all existing Markers
RemoveMarkers();
SetWait = 50; // Reduce wait between checks for new action file
break;
case 'PLOT': // PLOT details for multiple Markers
var iEntry = LatLong.length;
LatLong[iEntry] = NewLatLong(Value1,Value2);
LocName[iEntry] = Value3; // -- V4.2
Caption[iEntry] = Value4;
InfoWin[iEntry] = Value5; // -- V4.2
break;
case 'DROP': // DROP multiple Markers in chosen style
SetAnimation(Value1); // Set Marker Animation mode Value1 is 'null' for No Animation, or 'DROP' for Synchro/Cascade
SetDelay = parseInt(Value2); // Value2 is ~300 for No Animation/Synchro, or ~2000 for Cascade
if ( isNaN(SetDelay) ) { SetDelay = 300; }
SetBound = parseFloat(Value3); // Value3 is Bound offset -- V4.3
BanZoom = 99;
MakeMarkers();
PresentMap();
GetZoomLevel();
BanZoom = GetZoom;
SetWait = SetDelay; // Increase wait between checks for new action file -- V6.0
break;
case 'ZOOM': // ZOOM to chosen map Zoom level
SetZoomLevel(Value1);
NewZoom(SetZoom); // -- V5.3
MarkZoom = SetZoom; // Remember 'MARK' RepresentMap() Zoom level
BanZoom = 0;
SetWait = 250; // Increase wait between checks for new action file
break;
}
SendZoom(SetZoom); // Send synch char + map zoom level via HTML page
LoadFile('{plugin}.act');
}
function RemoveMarkers() // Remove all Marker and LatLong entries from map
{
for ( var iEntry = 0; iEntry < LatLong.length; iEntry++ )
{
RubMarker(iEntry);
}
MapMark.length = 0;
LatLong.length = 0;
}
function GetZoomLevel() // Get & send map Zoom level change
{
GetZoom = OldZoom(); // -- V5.3
if ( GetZoom>0 && BanZoom<30 && GetZoom!=BanZoom ) // Inhibit values after MARK missing Lat/Lng and DROP sample Markers
{
SetZoom = GetZoom;
SendZoom(SetZoom); // Send synch char + map zoom level via HTML page
BanZoom = GetZoom;
}
}
</script>
<style>
body { overflow: hidden; }
</style>
</head>
<body scroll='no'>
<div id='map' style='left: 1%; {mappane}'> <!–– Google/Leaflet Map Pane -- V5.3 ––>
</div>
<div id='status' style='width:100%; height:0%;' hidden> <!–– Status is hidden underneath map ––>
<p>~</p>
</div>
</body>
</html>
]=]
-- mapfacts.css lines derived from fhstyle.css except for #facts, #map, @keyframes, and firefox scrollbar fix -- V3.7 -- V3.9 -- V4.3
-- html, body style from https://developers.google.com/maps/documentation/javascript/examples/default.css needed for both Google and Leaflet map modes -- V4.3
-- These apply for standalone map pages but are overridden by FH web page <head> style
StrMasterCSS = [=[
body {padding-right: 0px; padding-left: 0px; font-size: 80%; padding-bottom: 0px; margin: 0px; color: #000000; padding-top: 0px; font-family: Verdana, Arial, sans-serif; background-color: #FFFFFF; }
html, body {height: 100%; margin: 0; padding: 0; }
a:link {color: #808080; font-weight: bold; text-decoration: none; }
a:visited {color: #808080; font-weight: bold; text-decoration: none; }
a:hover { color: #515151; font-weight: bold; text-decoration: none; }
a:active { color: #515151; font-weight: bold; text-decoration: none; }
.fhcontent { margin-left: 20px; margin-right: 20px; margin-top: 20px; margin-bottom: 20px; }
.FhHdg1 { clear: both; height: auto; font-size: 20pt; font-weight: 700; border-style: solid; border-top-width: 1pt; border-right-width: 0px; border-bottom-width: 1pt; border-left-width: 0px; margin-top: 24pt; margin-bottom: 8pt; font-family: Verdana, Arial, Helvetica, sans-serif; color: #000000; }
#facts {width: 49%; float: left; padding-right: 1%; }
#map {width: 49%; height: 80%; position: absolute; right: 1%; border: 1px solid grey; }
.noinfoscrollbar {line-height: 1.35; overflow: hidden; white-space: nowrap; }
/* Leaflet Maps Animation */
@keyframes drop-L { from { top: -400px; opacity: 1; } to { top: 0px; opacity: 1; } }
.DROP-L {animation: drop-L 0.5s 1; }
@keyframes flash-L { from { opacity: 0.1; } to { opacity: 1.0; } }
.FLASH-L {animation: flash-L 0.3s 6 alternate; }
@keyframes bounce-L { from { top: 0px; } to { top: -20px; } }
.BOUNCE-L {animation: bounce-L 0.3s cubic-bezier(0.5,0.05,1,0.5) 2 alternate; }
/* Google Maps Animation */
/* set the default transition time */
:root {
--delay-time: 0.5s;
}
@keyframes drop-G {
0% {
transform: translateY(-200px) scaleY(0.9);
opacity: 0;
}
5% {
opacity: 0.7;
}
50% {
transform: translateY(0px) scaleY(1);
opacity: 1;
}
65% {
transform: translateY(-17px) scaleY(0.9);
opacity: 1;
}
75% {
transform: translateY(-22px) scaleY(0.9);
opacity: 1;
}
100% {
transform: translateY(0px) scaleY(1);
opacity: 1;
}
}
.DROP-G {
animation: drop-G 0.3s linear forwards var(--delay-time);
}
@keyframes bounce-G {
0% {
transform: translateY(0px) scaleY(1);
opacity: 0;
}
20% {
opacity: 0;
}
65% {
transform: translateY(-17px) scaleY(0.9);
opacity: 1;
}
75% {
transform: translateY(-22px) scaleY(0.9);
opacity: 1;
}
100% {
transform: translateY(0px) scaleY(1);
opacity: 1;
}
}
.BOUNCE-G {
animation: bounce-G 0.3s linear forwards var(--delay-time);
}
/* Is this needed? */
.scaleStyle:hover {
-webkit-transition: 0.2s ease-in-out;
-moz-transition: 0.2s ease-in-out;
-o-transition: 0.2s ease-in-out;
transition: 0.2s ease-in-out;
-webkit-transform: scale(2);
-moz-transform: scale(2);
-o-transform: scale(2);
-ms-transform: scale(2);
transform: scale(2);
}
]=]
StrMasterJS = [=[
// Map Life Facts shared JavaScript
function MakeArrays() // Declare map Marker data arrays
{
MapMark = new Array(); // google.maps.Marker overlays
LatLong = new Array(); // google.maps.LatLng positions
LocName = new Array(); // InfoWindow location place name -- V4.2
Caption = new Array(); // Marker caption title tooltips
InfoWin = new Array(); // Google & Leaflet Maps InfoWindow popups
}
function OfflineError(sDivId) // Offline error message
{
document.getElementById(sDivId).innerHTML = '<p>Sorry, your PC appears to be offline, so maps cannot be shown ! ! !<\/p>';
}
function CheckOnline(no,yes) // Check if PC online
{
var xhr = new XMLHttpRequest();
xhr.onload = function() { yes(); }
xhr.onerror = function() { no(); }
xhr.open("GET","https://www.google.com/"); // Replace obsolete google.co.uk with google.com -- V6.0
xhr.send();
}
function InitialMap() // Initialise Map pane, Zoom level, and Marker Animation & Delay, then display Map
{
SynNew = ''; // Inhibit SendStatus() in 'Web Page' mode -- V4.3
Animation = null; // Marker animation mode
SetAnimation('{animate}'); // 'null'=No Animation, or 'DROP'=Synchro/Cascade
SetBound = null; // Bounds offset to present Markers well within Map -- V4.3
SetPause = 900; // Pause for 900ms before dropping initial Markers
SetDelay = parseInt({timeout}); // Marker delay of ~300ms=No Animation/Synchro, or ~2000ms=Cascade
if ( isNaN(SetDelay) ) {SetDelay = 300;}
SetZoom = 12; // Current map Zoom level
SetZoomLevel({setzoom}); // Integer between 0 and 22
MarkZoom = SetZoom; // Default zoom level for single 'MARK' Markers
Home = NewLatLong(5.65,0.0); // Tema, Accra, Ghana is nearest urban area to (0.0,0.0) N.B. Greenwich = (51.5,0.0)
MakeMapPane('Web Page'); // Make the map pane display for 'Web Page' mode
MakeMarkers();
PresentMap();
SetPause = 300; // Pause for 300ms before dropping Markers
}
function MakeMarkers() // Make the InfoWindow popups, and create empty Markers
{
for ( var iEntry = 0; iEntry < LatLong.length; iEntry++ )
{
var latlng = GetLatLong(iEntry);
InfoWin[iEntry] = Respace(InfoWin[iEntry]) + '<br />' + Respace(LocName[iEntry]) + '<br />Latitude : ' + latlng[0] + '<br />Longitude : ' + latlng[1];
Caption[iEntry] = Caption[iEntry].replace(/\01as/g,'&'); // Convert ampersand
Caption[iEntry] = Caption[iEntry].replace(/\01lt/g,'<'); // Convert less than
Caption[iEntry] = Caption[iEntry].replace(/\01gt/g,'>'); // Convert more than
NewMarker(iEntry);
}
}
function RepresentMap() // Represent original zoomed out Map & Marker(s) after Map click
{
if ( LatLong.length == 0 )
{
BanZoom = 1; // Lat/Long missing so Zoom out and pan to Home
PanTo(Home,1);
}
else
if ( SetDelay == 0 )
{
PanTo(LatLong[0],MarkZoom); // Display single Marker created by MARK
DropMarker(0);
BanZoom = 0;
}
else
{
BanZoom = 99; // Display multiple Markers created by DROP
PresentMap();
GetZoom = OldZoom();
BanZoom = GetZoom;
}
}
function PresentMap() // Plot map Markers and adjust map Bounds
{
try
{
if ( LatLong.length == 1 )
{
PanTo(LatLong[0],MarkZoom); // Display single Marker created by MARK -- V5.3
DropMarker(0);
BanZoom = 0;
}
else
{
NewBounds();
for ( var iEntry = 0; iEntry < LatLong.length; iEntry++ )
{
if ( LatLong[iEntry] != 0 ) // Set map Markers and Bounds for Latitude & Longitude
{
DropMarker(iEntry);
SetBounds(iEntry); // Set bounds so all Markers are included
}
}
FitBounds(); // -- V5.3
}
}
catch(error){return 'Fail';}
}
function DropMarker(iEntry) // After delay, drop a map Marker at Latitude & Longitude with a tooltip Caption, and InfoWindow popup on click
{
RubMarker(iEntry); // Clear old Marker and Drop new Marker
setTimeout(function(){AddMarker(iEntry);},iTimeout(iEntry));
}
function iTimeout(iEntry) // Calculate time delay for Marker
{
var iDelay = SetDelay; // Immediate/No Animation/Synchro delay is fixed
if ( SetDelay > 500 )
{
if ( iEntry < 10 )
{
iDelay = SetDelay * iEntry / 10; // Cascade delay increased for each Marker
}
else
{
iDelay = SetDelay * Math.random(); // Cascade delay random when many Markers
}
}
return SetPause + iDelay;
}
function SetZoomLevel(iZoom) // Set the map Zoom level
{
SetZoom = parseInt(iZoom);
if ( isNaN( SetZoom ) ) {SetZoom=12;}
if ( SetZoom < MinZoom ) {SetZoom=MinZoom;}
if ( SetZoom > MaxZoom ) {SetZoom=MaxZoom;}
}
function ZoomToMarker(iEntry) // Zoom to Marker or show whole map
{
if ( LatLong[iEntry] == 0 )
{
FitBounds() // Missing Latitude & Longitude so show whole map -- V5.3
}
else
{
FlyTo(LatLong[iEntry],MarkZoom); // Zoom and pan to Marker, and bounce it for 3 secs
SetMarker(iEntry);
}
}
function fRoundNumber(fNumber,iDecPoints) // Round floating Number to chosen Decimal Points
{
var iPower = Math.pow(10,iDecPoints);
var fAns = Math.round(fNumber * iPower) / iPower;
return fAns;
}
function SendStatus(message) // Send synch char + status message hidden below GUI IUP frame
{
if ( message == undefined ) {message=' ';}
if ( SynNew.length == 1 ) { // Only in 'Plugin' mode -- V4.3
document.getElementById('status').innerHTML = '<p>' + SynNew + message + '<\/p>';
}
}
function SendZoom(zoom) // Send synch char + map zoom level via HTML page -- V4.3
{
if ( zoom == undefined ) {zoom=12;}
SendStatus(' Zoom=' + zoom);
}
function SendLatLng(lat,lng,act) // Send synch char + Lat & Lng & Act via HTML page -- V4.3
{
SendStatus(' Lat=' + fRoundNumber(lat,7) + ' Lng=' + fRoundNumber(lng,7) + ' Act=' + act);
}
{mapmode}
]=]
-- Google Maps (Legacy) custom JavaScript -- V4.3
StrGoogleJS = [=[
MinZoom = 1;
MaxZoom = 22;
function MakeMapPane(mode)
{ // Note overviewMapControl:true fails when Zoom slider moved up rapidly sometimes!
Pane = new google.maps.Map(document.getElementById('map'),{zoom:SetZoom,center:Home,overviewMapControl:true,scaleControl:true,mapTypeId:google.maps.MapTypeId.TERRAIN});
Pane.addListener('click',function(){RepresentMap();}); // Show original map on click
Pane.addListener('zoom_changed',function(){ZoomMarkers();}); // On zoom change resize marker pins -- V6.0
}
function Respace(sText) // Respace text
{
return sText
}
function NewBounds() // New map Bounds
{
Bounds = new google.maps.LatLngBounds();
if ( isNaN(parseFloat(SetBound)) ) {SetBound=0.2;} // -- V4.3
}
function SetBounds(iEntry) // Set map Bounds
{
Bounds.extend(LatLong[iEntry]);
var ne = Bounds.getNorthEast();
var sw = Bounds.getSouthWest();
var n = ne.lat() - SetBound;
var e = ne.lng() - SetBound; // Enlarge bounds by 'SetBound' in each direction
var s = sw.lat() + SetBound;
var w = sw.lng() + SetBound;
Bounds.extend(NewLatLong(n,e));
Bounds.extend(NewLatLong(s,w));
}
function FitBounds() // Fit map Bounds -- V5.3
{
Pane.fitBounds(Bounds);
}
function SetAnimation(sMode) // Set the Marker Animation mode
{
Animation = null;
if ( sMode == 'DROP' ) // 'null'=No Animation, or 'DROP'=Synchro/Cascade
{
Animation = google.maps.Animation.DROP;
}
}
function OldZoom() // Get current Zoom level
{
return Pane.getZoom();
}
function NewZoom(iZoom) // Set the new Zoom level
{
Pane.setZoom(iZoom);
}
function FlyTo(latlng,iZoom) // Pan & Zoom to Lat/Long position smoothly
{
PanTo(latlng,iZoom);
}
function PanTo(latlng,iZoom) // Pan & Zoom to Lat/Long position directly
{
setTimeout(function(){Pane.panTo(latlng);setTimeout(function(){Pane.setZoom(iZoom);},100);},150); // Leaflet Maps use 100 & 400
}
function NewMarker(iEntry) // New map Marker
{
InfoWin[iEntry] = new google.maps.InfoWindow({content:InfoWin[iEntry],headerContent:'Location Details'}); // V6.0
MapMark[iEntry] = new google.maps.Marker();
}
function MakeIcon() // Make marker icon scaled to zoom -- V6.0
{
var iZoom = Math.min(Math.max(Pane.getZoom(),5),13); // Limit size to Zoom levels 5 to 13
var iSizeWide = 25 / 12 * iZoom;
var iSizeHigh = 41 / 12 * iZoom;
var iAnchor = iSizeHigh+iZoom-6; // Prevent icon Achor point drifting north as map Zooms in
var icon = {url:'mapfacts.png',scaledSize:new google.maps.Size(iSizeWide,iSizeHigh),anchor:new google.maps.Point(iSizeWide/2,iAnchor)};
//Defaults {url:'mapfacts.png',scaledSize:new google.maps.Size(25,41),anchor:new google.maps.Point(12,40)};
return icon
}
function ZoomMarkers() // Zoom the marker pins icon size -- V6.0
{
for ( var iEntry = 0; iEntry < MapMark.length; iEntry++ )
{
if ( LatLong[iEntry] != undefined && MapMark[iEntry].setIcon != undefined ) {MapMark[iEntry].setIcon(MakeIcon());}
}
}
function AddMarker(iEntry) // Add draggable map Marker with Tooltip caption and Popup info
{
SendZoom(SetZoom);
MapMark[iEntry] = new google.maps.Marker({map:Pane,position:LatLong[iEntry],icon:MakeIcon(),draggable:true,title:Caption[iEntry],animation:Animation});
MapMark[iEntry].addListener('click' ,function(){InfoWin[iEntry].open(Pane,MapMark[iEntry]);FlyTo(LatLong[iEntry],MarkZoom);});
MapMark[iEntry].addListener('drag' ,function(event){SendAct(event,'Mid');}); // Plotting by dragging -- V4.3
MapMark[iEntry].addListener('dragend',function(event){SendAct(event,'End');}); // Plotting by dragging
}
function SendAct(event,act) // Send plotting event Lat/Lng
{
var latlng = event.latLng;
SendLatLng(latlng.lat(),latlng.lng(),act);
}
function SetMarker(iEntry) // Agitate map Marker
{
MapMark[iEntry].setAnimation(google.maps.Animation.BOUNCE);
setTimeout(function(){MapMark[iEntry].setAnimation(null);},3000);
}
function RubMarker(iEntry) // Rubout map Marker
{
if ( MapMark[iEntry] != 0 ) // V5.0 like leaflet.js
{
try { MapMark[iEntry].setMap(null); } // V5.0 try & finally to avoid any error message
catch(e){ }
finally { MapMark[iEntry] = 0 ; } // V4.6
}
}
function MoveMarker(iEntry) // Move map Marker without animation
{
try{ MapMark[iEntry].setPosition(LatLong[iEntry]); } // V5.0 try to avoid any error message
catch(e){ }
try{ MapMark[iEntry].setMap(Pane); } // V5.0 try to avoid any error message
catch(e){ }
}
function NewLatLong(lat,lng) // New latitude,longitude
{
return new google.maps.LatLng(lat,lng);
}
function GetLatLong(iEntry) // Get latitude,longitude
{
var fLat = LatLong[iEntry].lat();
var fLng = LatLong[iEntry].lng();
return [fRoundNumber(fLat,7),fRoundNumber(fLng,7)];
}
async function InitMap() // See https://developers.google.com/maps/documentation/javascript/load-maps-js-api -- V6.0
{
Initialise(); // Call shared script
}
document.write( // Google java script -- V4.5 &callback=Initialise' async defer -- V4.9 Use v=3.45 for IE v=weekly -- V6.0
"<script type='text/javascript' src='https://maps.googleapis.com/maps/api/js?v=weekly&key={api_key}&loading=async&callback=InitMap'> </script>"
);
]=]
-- Google Maps Advanced Marker custom JavaScript -- V6.0
StrAdvanceJS = [=[
MinZoom = 1;
MaxZoom = 22;
function MakeMapPane(mode)
{ // Note overviewMapControl:true fails when Zoom slider moved up rapidly sometimes!
Pane = new google.maps.Map(document.getElementById('map'),{mapId:'{map_id#}',zoom:SetZoom,center:Home,overviewMapControl:true,scaleControl:true,mapTypeId:google.maps.MapTypeId.TERRAIN});
Pane.addListener('click',function(){RepresentMap();}); // Show original map on click
Pane.addListener('zoom_changed',function(){ZoomMarkers();}); // On zoom change resize marker pins -- V6.0
}
function Respace(sText) // Respace text
{
return sText
}
function NewBounds() // New map Bounds
{
Bounds = new google.maps.LatLngBounds();
if ( isNaN(parseFloat(SetBound)) ) {SetBound=0.2;} // -- V4.3
}
function SetBounds(iEntry) // Set map Bounds
{
Bounds.extend(LatLong[iEntry]);
var ne = Bounds.getNorthEast();
var sw = Bounds.getSouthWest();
var n = ne.lat() - SetBound;
var e = ne.lng() - SetBound; // Enlarge bounds by 'SetBound' in each direction
var s = sw.lat() + SetBound;
var w = sw.lng() + SetBound;
Bounds.extend(NewLatLong(n,e));
Bounds.extend(NewLatLong(s,w));
}
function FitBounds() // Fit map Bounds -- V5.3
{
Pane.fitBounds(Bounds);
}
function SetAnimation(sMode) // Set the Marker Animation mode
{
Animation = null;
if ( sMode == 'DROP' ) // 'null'=No Animation, or 'DROP'=Synchro/Cascade
{
Animation = 'DROP-G';
}
}
function OldZoom() // Get current Zoom level
{
return Pane.getZoom();
}
function NewZoom(iZoom) // Set the new Zoom level
{
Pane.setZoom(iZoom);
}
function FlyTo(latlng,iZoom) // Pan & Zoom to Lat/Long position smoothly
{
PanTo(latlng,iZoom);
}
function PanTo(latlng,iZoom) // Pan & Zoom to Lat/Long position directly
{
setTimeout(function(){Pane.panTo(latlng);setTimeout(function(){Pane.setZoom(iZoom);},100);},150); // Leaflet Maps use 100 & 400
}
function NewMarker(iEntry) // New map Marker
{
InfoWin[iEntry] = new google.maps.InfoWindow({content:InfoWin[iEntry],headerContent:'Location Details'}); // V6.0
MapMark[iEntry] = new google.maps.marker.AdvancedMarkerElement();
}
function IconScale() // Set marker icon scale for the zoom -- V6.0
{
var iZoom = Math.min(Math.max(Pane.getZoom(),5),15); // Limit scale to Zoom levels 5 to 15
return iZoom / 12;
}
function ZoomMarkers() // Zoom the marker pins icon size -- V6.0
{
for ( var iEntry = 0; iEntry < MapMark.length; iEntry++ )
{
if ( LatLong[iEntry] != undefined && MapMark[iEntry].content != undefined ) {MapMark[iEntry].content.scale = IconScale();}
}
}
function AddMarker(iEntry) // Add draggable map Marker with Tooltip caption and Popup info
{
SendZoom(SetZoom);
// Set the marker pin colours and scale
var markerStyle = new google.maps.marker.PinElement({background:'{colours}',borderColor:'Dark{colours}',glyphColor:'Dark{colours}',scale:IconScale()});
MapMark[iEntry] = new google.maps.marker.AdvancedMarkerElement({map:Pane,position:LatLong[iEntry],gmpDraggable:true,title:Caption[iEntry]});
MapMark[iEntry].content = markerStyle.element;
MapMark[iEntry].addListener('click' ,function(){InfoWin[iEntry].open(Pane,MapMark[iEntry]);FlyTo(LatLong[iEntry],MarkZoom);});
MapMark[iEntry].addListener('drag' ,function(event){SendAct(event,'Mid');}); // Plotting by dragging -- V4.3
MapMark[iEntry].addListener('dragend',function(event){SendAct(event,'End');}); // Plotting by dragging
if ( LatLong.length == 1 ) {Pane.setZoom(MarkZoom);}
if ( Animation == 'DROP-G' )
{
MapMark[iEntry].content.style.opacity = '0';
MapMark[iEntry].content.classList.add(Animation);
MapMark[iEntry].content.addEventListener('animationend', (event) => {
MapMark[iEntry].content.classList.remove(Animation);
MapMark[iEntry].content.style.opacity = '1';
});
}
}
function SendAct(event,act) // Send plotting event Lat/Lng
{
var latlng = event.latLng;
SendLatLng(latlng.lat(),latlng.lng(),act);
}
function SetMarker(iEntry) // Agitate map Marker
{
MapMark[iEntry].content.classList.add('BOUNCE-G');
MapMark[iEntry].content.addEventListener('animationend', (event) => {
MapMark[iEntry].content.classList.remove('BOUNCE-G');
MapMark[iEntry].content.style.opacity = '1';
});
}
function RubMarker(iEntry) // Rubout map Marker
{
if ( MapMark[iEntry] != 0 ) // V5.0 like leaflet.js
{
try { MapMark[iEntry].setMap(null); } // V5.0 try & finally to avoid any error message
catch(e){ }
finally { MapMark[iEntry] = 0 ; } // V4.6
}
}
function MoveMarker(iEntry) // Move map Marker without animation
{
try{ MapMark[iEntry].setPosition(LatLong[iEntry]); } // V5.0 try to avoid any error message
catch(e){ }
try{ MapMark[iEntry].setMap(Pane); } // V5.0 try to avoid any error message
catch(e){ }
}
function NewLatLong(lat,lng) // New latitude,longitude
{
return new google.maps.LatLng(lat,lng);
}
function GetLatLong(iEntry) // Get latitude,longitude
{
var fLat = LatLong[iEntry].lat();
var fLng = LatLong[iEntry].lng();
return [fRoundNumber(fLat,7),fRoundNumber(fLng,7)];
}
async function InitMap() // See https://developers.google.com/maps/documentation/javascript/load-maps-js-api -- V6.0
{
await google.maps.importLibrary("maps"); // Import Google Maps Libraries
await google.maps.importLibrary("marker");
Initialise(); // Call shared script
}
document.write( // Google Maps java script -- V4.5 &loading=async&callback=InitMap -- V6.0
"<script async type='text/javascript' src='https://maps.googleapis.com/maps/api/js?v=weekly&key={api_key}&loading=async&callback=InitMap'></script>"
);
]=]
-- Leaflet Maps map pane {mapmode} custom JavaScript -- V4.3
StrLeafletJS = [=[
MinZoom = 1;
MaxZoom = 19;
function MakeMapPane(mode) // Make map pane where mode is 'Plugin' or 'Web Page'
{
// See 'Control.Layers' and 'Layer Groups and Layers Control' and 'Basemap Providers' to switch between alternative base layer maps, etc.
var osmapUrl = 'https://tile.openstreetmap.org/{z}/{x}/{y}.png'; // OpenStreetMap alternative
var osmapAtt = '© <a href="https://www.openstreetmap.org/about/" target="_new">OpenStreetMap</a> contributors';
var osmapLay = L.tileLayer(osmapUrl,{attribution:osmapAtt,maxZoom:19,minZoom:1});
var mbsatKey = 'pk.eyJ1IjoidGF0ZXdpc2UiLCJhIjoiY2ptNmNkZndzM2lhczN3cXV2ODZoeGk4MyJ9.dmr-Fwb6Ll2X6ICPl7AVkQ'; // Mapbox Satellite alternative
var mbsatUrl = 'https://api.mapbox.com/v4/mapbox.satellite/{z}/{x}/{y}.png?access_token=' + mbsatKey; // id: mapbox.satellite Nov 2020
var mbsatAtt = 'Map data © <a href="https://www.openstreetmap.org/about/" target="_new">OpenStreetMap</a> contributors, ' +
'<a href="https://creativecommons.org/licenses/by-sa/2.0/" target="_new">CC-BY-SA</a>, Imagery © <a href="https://www.mapbox.com/ target="_new"">Mapbox</a>';
var mbsatLay = L.tileLayer(mbsatUrl,{attribution:mbsatAtt,maxZoom:19,minZoom:1});
var googlUrl = 'https://mt.google.com/vt/lyrs={layers}&x={x}&y={y}&z={z}'; // Google Maps alternatives -- V6.0
var googlAtt = '© Google Maps'; // https://stackoverflow.com/questions/9394190/leaflet-map-api-with-google-satellite-layer
var gmapsLay = L.tileLayer(googlUrl,{attribution:googlAtt,maxZoom:19,minZoom:1,layers:'m'}); // Google Street Maps Default
var gterrLay = L.tileLayer(googlUrl,{attribution:googlAtt,maxZoom:19,minZoom:1,layers:'p'}); // Google Streets with Terrain
var gsatlLay = L.tileLayer(googlUrl,{attribution:googlAtt,maxZoom:19,minZoom:1,layers:'y'}); // Google Satellite with Labels
// "lyrs" parameter in URL: Street Maps: m Terrain & Labels: p Satellite Only: s Satellite & Labels: y Roads Only: h Hybrid: s,h Terrain Only: t Traffic overlay: m@221097413,traffic Altered Road Map: r
var layers = { // Assign names to the base layers
'Open Street Map' :osmapLay,
'Mapbox Satellite':mbsatLay,
'Google Streets' :gmapsLay,
'Google Terrain' :gterrLay,
'Google Satellite':gsatlLay,
};
var base = '{basemap}'; // Chosen base layer map
if ( layers[base] == undefined ) {base='Open Street Map';}
Pane = L.map('map',{center:Home,zoom:SetZoom,zoomSnap:0.5,doubleClickZoom:false,layers:[layers[base]]});
L.control.layers(layers).addTo(Pane); // Add base layer maps with default chosen by layers[base] above
L.control.scale().addTo(Pane); // Add scale bar
Pane.on('click',function(){RepresentMap();}); // Show original map on click
if ( mode == 'Plugin' )
{
Pane.on('baselayerchange',function(event){SendStatus(' Base=' + event.name);}); // Log new base layer map when changed
Pane.on('rightclick',function(event){SendLatLng(event.latlng.lat,event.latlng.lng,'New');}); // Add new Marker on right-click (not implemented)
Pane.on('dblclick' ,function(event){SendLatLng(event.latlng.lat,event.latlng.lng,'New');}); // Add new Marker on double-click
Pane.on('zoomend',function(){GetZoomLevel();ZoomMarkers();}); // On zoom change log zoom level and resize marker pins -- V6.0
}
else
{
Pane.on('zoomend',function(){ZoomMarkers();}) // On zoom change resize marker pins -- V6.0
}
}
function Respace(sText) // Respace text
{
return sText
}
function NewBounds() // New map Bounds
{
Bounds = L.latLngBounds();
if ( isNaN(parseFloat(SetBound)) ) {SetBound=0.3;} // -- V4.3
}
function SetBounds(iEntry) // Extend map Bounds
{
Bounds.extend(LatLong[iEntry]);
var n = Bounds.getNorth() - SetBound;
var e = Bounds.getEast() - SetBound; // Enlarge bounds by 'SetBound' in each direction
var s = Bounds.getSouth() + SetBound;
var w = Bounds.getWest() + SetBound;
Bounds.extend(NewLatLong(n,e));
Bounds.extend(NewLatLong(s,w));
}
function FitBounds() // Fit map Bounds -- V5.3
{
Pane.fitBounds(Bounds);
}
function SetAnimation(sMode) // Set the Marker Animation mode
{
Animation = null;
if ( sMode == 'DROP' ) // 'null'=No Animation, or 'DROP'=Synchro/Cascade
{
Animation = 'DROP-L';
}
}
function OldZoom() // Get current Zoom level
{
return Pane.getZoom();
}
function NewZoom(iZoom) // Set the new Zoom level
{
Pane.setZoom(iZoom);
}
function FlyTo(latlng,iZoom) // Pan & Zoom to Lat/Long position smoothly with timeout to allow large changes
{
if ( latlng != 0 )
{
setTimeout(function(){Pane.flyTo(latlng,iZoom);},200); // options: ,{animate:true,duration:2.5}
}
}
function PanTo(latlng,iZoom) // Pan & Zoom to Lat/Long position directly with timeout to allow large changes
{
if ( latlng != 0 )
{
setTimeout(function(){Pane.panTo(latlng);setTimeout(function(){Pane.setZoom(iZoom);},100);},400); // -- was 200 & 200 -- Google Maps use 100 & 150 -- V4.9
// if ( Pane.getZoom() != iZoom ) {Pane.setZoom(iZoom);}
}
}
function NewMarker(iEntry) // New map Marker
{
MapMark[iEntry] = L.marker(LatLong[iEntry]);
}
function MakeIcon() // Make marker icon scaled to zoom -- V6.0
{
var iZoom = Math.min(Math.max(Pane.getZoom(),5),13); // Limit size to Zoom levels 5 to 13
var iSizeWide = 25 / 12 * iZoom;
var iSizeHigh = 41 / 12 * iZoom;
var iAnchor = iSizeHigh+iZoom-6; // Prevent icon Achor point drifting north as map Zooms in
var icon = L.icon({iconUrl:'mapfacts.png',iconSize:[iSizeWide,iSizeHigh],iconAnchor:[iSizeWide/2,iAnchor],popupAnchor:[0,-iSizeHigh],tooltipAnchor:[0,0],className:Animation});
//Defaults L.icon({iconUrl:'mapfacts.png',iconSize:[25,41],iconAnchor:[12,40],popupAnchor:[0,-40],tooltipAnchor:[0,0],className:Animation});
// Leaflet L.icon({iconUrl:'https://unpkg.com/leaflet@1.9.4/dist/images/marker-icon.png',iconAnchor:[20,50],popupAnchor:[-8,-52],className:Animation});
return icon
}
function ZoomMarkers() // Zoom the marker pins icon size -- V6.0
{
for ( var iEntry = 0; iEntry < MapMark.length; iEntry++ )
{
if ( LatLong[iEntry] != undefined && MapMark[iEntry].setIcon != undefined ) {MapMark[iEntry].setIcon(MakeIcon());}
}
}
function AddMarker(iEntry) // Add draggable map Marker with Tooltip caption and Popup info
{
SendZoom(SetZoom);
RubMarker(iEntry);
MapMark[iEntry] = L.marker(LatLong[iEntry],{icon:MakeIcon(),draggable:true,autoPan:true,title:Caption[iEntry]}).bindPopup(InfoWin[iEntry]); // ,title:Caption[iEntry] or .bindTooltip(Caption[iEntry])
MapMark[iEntry].addTo(Pane);
MapMark[iEntry].on('click' ,function(){FlyTo(LatLong[iEntry],MarkZoom);});
MapMark[iEntry].on('drag' ,function(){SendAct(iEntry,'Mid');}); // Plotting by dragging -- V4.3
MapMark[iEntry].on('dragend',function(){SendAct(iEntry,'End');}); // Plotting by dragging
if ( LatLong.length == 1 ) {Pane.setZoom(MarkZoom);}
if ( Animation == 'DROP-L' ) {SetMarker(iEntry);}
}
function SendAct(iEntry,act) // Send dragging event Lat/Lng
{
var latlng = MapMark[iEntry].getLatLng();
SendLatLng(latlng.lat,latlng.lng,act);
}
function SetMarker(iEntry) // Agitate map Marker and remove style classes
{
var move = 'BOUNCE-L'; // 'BOUNCE-L' or 'FLASH-L'
var error = false; // Repeat L.DomUtil as _icon may take time to appear
do {
error = false;
setTimeout( function()
{ try { L.DomUtil.addClass(MapMark[iEntry]._icon,move); } catch(err) {error=true;} }, 300);
} while(error);
do {
error = false;
setTimeout( function()
{ try { L.DomUtil.removeClass(MapMark[iEntry]._icon,move); L.DomUtil.removeClass(MapMark[iEntry]._icon,Animation); } catch(err) {error=true;} }, 2000);
} while(error);
}
function RubMarker(iEntry) // Rubout map Marker
{
if ( MapMark[iEntry] != 0 )
{
try{ MapMark[iEntry].remove(); } // V5.0 try to avoid any error message
catch(e){ }
}
}
function MoveMarker(iEntry) // Move map Marker without animation
{
try{ MapMark[iEntry].setLatLng(LatLong[iEntry]); } // V5.0 try to avoid any error message
catch(e){ }
try{ MapMark[iEntry].addTo(Pane); } // V5.0 try to avoid any error message
catch(e){ }
}
function NewLatLong(lat,lng) // New latitude,longitude
{
return L.latLng(lat,lng);
}
function GetLatLong(iEntry) // Get latitude,longitude
{
var fLat = LatLong[iEntry].lat;
var fLng = LatLong[iEntry].lng;
return [fRoundNumber(fLat,7),fRoundNumber(fLng,7)];
}
function InitMap() // See https://leafletjs.com/examples/quick-start/ -- V6.0
{
Initialise(); // Call shared script
}
document.write( // Leaflet style sheet and java script
"<link type='text/css' rel='stylesheet' href='https://unpkg.com/leaflet@1.9.4/dist/leaflet.css' integrity='sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=' crossorigin=''/>" +
"<script type='text/javascript' src='https://unpkg.com/leaflet@1.9.4/dist/leaflet.js' integrity='sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=' crossorigin=''> </script>"
);
window.onload = function(){ InitMap(); }; // V6.0 equivalent to Google Maps &loading=async&callback=InitMap
]=]
-- HTML script template for displying web page maps
-- Make the map page style even more like the FH web page style -- V3.7
-- Moved {fhheaders} later in <head> to override mapfacts.css
-- Added <div class="fhcontent"> <h1 class="FhHdg1"> to heading
StrMasterWeb = [=[
<!DOCTYPE html>
<html>
<head>
<title>Map of Locations for {titlename}</title>
<meta name='viewport' content='initial-scale=1.0,user-scalable=no'>
<link type='text/css' rel='stylesheet' href='mapfacts.css'>
{fhheaders}
<script type='text/javascript' src='mapfacts.js'> </script>
<script type='text/javascript'>
function Initialise()
{
MakeArrays();
{javalists}
InitialMap();
}
</script>
</head>
<body>{fhmenubar}
<div class="fhcontent">
<h1 class="FhHdg1">
<a href='#' onclick='PresentMap()'>Map of Locations for {titlename}</a>
</h1>
</div>
<div id='facts'>
<ul>
{factslist}
</ul>
</div>
<div id='map'>
</div>
</body>
</html>
]=]
-- Provide scripts for {mapmode} in StrMasterJS above chosen by IntMapMode setting
-- Each script fragment is dedicated to one map pane mode -- V4.3
StrMasterMap = {
GoogleLegacy = StrGoogleJS ; -- Google Maps Legacy Marker map pane mode JavaScript
LeafletMaps = StrLeafletJS ; -- Leaflet Maps map pane mode JavaScript
GoogleAdvanced = StrAdvanceJS ; -- Google Maps Advanced Marker map pane mode JavaScript -- V6.0
}
-- Main body of Plugin script starts here --
if fhGetAppVersion() > 7 then -- V6.1
fhMessageBox("\nThis plugin is not supported in Family Historian Version 8.\n","MB_OK","MB_ICONSTOP")
else
local strIupSystem = iup.GetGlobal("SYSTEM") -- Get the Windows System name -- V4.2
if strIupSystem:match("WinXP")
or strIupSystem:match("Vista") then
fhMessageBox("\nCurrent online mapping tools needed by this Plugin\ndo not support the Internet Explorer versions that\nare available with Windows XP and Windows Vista.\n","MB_OK","MB_ICONSTOP")
else
local intPause = collectgarbage("setpause",50) -- Default = 200 Aggressive = 50 -- Sets pause of collector and returns prev value of pause -- V4.9
local intStep = collectgarbage("setstepmul",300) -- Default = 200 Aggressive = 300 -- Sets step mult of collector & returns prev value of step -- V4.9
fhInitialise(5,0,9,"save_recommended")
PresetGlobalData() -- Preset global data values
ResetDefaultSettings() -- Preset default sticky settings
LoadSettings() -- Load sticky data settings
GUI_MainDialogue()
SaveSettings() -- Save sticky data settings
fhUpdateDisplay()
end
end
--[=[
@V6.1: Library 4.1; Centre windows on FH window; Fix FH V8 Language Pack tag; Inhibit FH V8; TBD: Fix crash; Handle FH V8 _ADDR records; io.open(strMasterPNG,"wb"); "_blank" > "_new";
@V6.0: In StrMasterJS CheckOnline(...) replace obsolete google.co.uk with google.com; Remove Bing Maps; Add Google Maps to Leaflet panes; Add pin colour options and scaled size; Add Check for Updates button;
Update Leaflet to unpkg.com/leaflet@1.9.4/dist from 1.7.1; Inhibit invalid or too precise Lat/Long values; Inhibit Enter/Return/Esc key from closing plugin; Fix Delete All Maps;
@V5.4: 'Copy Location to Standardized' button never changes Lat/Long or Status; StrGeocodeLocation(...) reports errors as per https://forums.indigorose.com/forum/autoplay-media-studio-8-5/autoplay-media-studio-8-discussion/297522-how-to-catch-an-error-from-a-plugin-luacom;
@V5.3: Bing Maps fully supported; Google Maps allowed for Web Page Maps; Geoapify geocoder added; Map Pane legend sizing for screen resoluton;
@V5.2: Deprecate the Google Maps display pane mode that no longer works; Remove the Leaflet Maps NLS Historic UK option;
@V5.1: Use library V3.3 with FSO; Cater for 'Improve Website or CD DVD HTML' plugin Tree Diagrams tab 'See also' section; Cater for foreign languages;
@V5.0: In StrGeocodeLocation(...) purge blank comma parts that upset Google Geocoder; Modify TblLocCode and add TblNation for conflicts and UK county abbreviations like Berks that upset place names elsewhere; If plot Not Found then prune leading location part; Use try & finally in Google & Leaflet JavaScript to avoid error mesages;
@V4.9: Fix LoadLocationsData(...) fhGetLabelledText() tab prefix; Revised splash Help page for new users; Handle "Blocked/No Auto-Geocode" Place record status & Block Geocoding button; Use FH names for Tentative, Standardized, etc; Aggressive memory management; CheckVersionInStore() at monthly intervals; Reverse Display Order option for Place List; Leaflets Maps PanTo() longer delay; Use Google Maps v=3.45 for IE;
@V4.8: Fix iup_gui.HelpDialogue(...) oversights;
@V4.7: Updated library to Functions Prototypes v3.0; iup_gui.Balloon = "NO" for PlayOnLinux/Mac; Fix problem swapping Locations From & Database In when GEDCOM records involved; Sample city plots based on Region Bias; Updated to unpkg.com/leaflet@1.7.1/dist from 1.0.3 & 1.3.4; Removed Mapbox Streets from Leaflet as not now free;
@V4.6: Fix problem with Google Maps not rubbing out markers correctly, allow all unreserved URI characters (-.~) in API Key, fix iup.TreeAddNodes() limits leaf Title to ~254 characters (Fixed: IUP v3.28) in doBuildTreeNodes() & strGetTreeLeaf() and failsafe in doRefreshLocation(), Library V2.9 update.
@V4.5: Fix problem with Google Maps API not allowing Map variable with same name as Map class, so altered to Pane; JavaScript added Google Maps '&callback async defer' and Leaflets Maps 'window.onload' to call Initialise().
@V4.4: Fix problem with detecting old format map files in FixOldMapFiles(...).
@V4.3: Ensure API Key is alphanumeric, in Place List disregard leading " , or space so grouped by first alphanumeric character, offer Leaflet Maps alternative to Google Maps JavaScript API, allow Markers to be dragged, and Google Maps to be disabled.
@V4.2: maps.googleapis.com/maps/api/ moved to StrMasterJS, uses https:, gains &key=StrAPI_Key, drops &sensor=false, plus improvements in StrGeocodeLocation(...), StrMasterHTM & StrMasterWeb drop v=StrApiVersion, EMIG/IMMI 2nd Place fix, better handling of &<>'" with StrTidyCommas() for map locations, new splash popups for API Key required, etc.
@V4.1: Updated library module for safer IUP GUI, and fix UTF8 characters in lists.
@V4.0: Exclude [[private]] text. Fix doEraseLocation() intFromTag bug.
@V3.9: Replace hyphen with space in StrGeocodeLocation() as workaround for Stoke-upon-Trent, change StrMasterHTM & StrMasterWeb to <!DOCTYPE html> and to use StrApiVersion, StrMasterCSS infomsg fix for IE8/9, fix 'No Plot' status in SaveLocationsData(), only use better data in StrGeocodeLocation(), unconditional button Geocode Plot This Location.
@V3.8: Handle UNKNOWN_ERROR geocode status, use maps.googleapis.com/maps/api/js?v=3.18 for IE v9, and remove Decimal point correction.
@V3.7: DicFactSet/StrFactSet/tglFactSet filter to limit maps to webpage Facts, and make map page CSS format match web page format more closely. Windows Decimal Symbol correction removed.
@V3.6: Cater for Decimal point symbol = dot or comma, fix Database Management tab problems, and minor Location/Individual list display glitches.
@V3.5: Reinstate Strikeout in GUI_PlotDialogue(), better order for Help pages including FAQ, fix spaces in Place Record names, use [unnamed person] when no Name, "List" dialogue position now sticky,
update all Lua % patterns, and StrEncode_URI/XML(), change "Manual" to "Defined"; "Next Plotted" only traverses "Plotted"; inhibit busy tabs, plus storage mode fixes, and remove old Place Record names.
@V3.4: Corrections to changing Options 'Locations From', 'Database In' and 'Restore Defaults' for FH V6 when 'Place Records' involved.
@V3.3: Check Version in Store, Progress Bar function prototype, needs V5.0.9 for Emigration/Immigration, add To/From _PLAC to Plugin Locations but not HTML Maps (TESTING looks OK), update FlgFolderExists(),
remove multiple "; " Place/Address column separators, use new plain string functions, use StrANSI_XML() and StrANSI_URI(), add Version History help, use new GUI library, minor updates,
detect lat/long [%+%-]?[0-9]-%.[0-9]-, [%+%-]?[0-9]-%.[0-9]- and use to exclusion of all else for Google Geocoder, XP setToolTip(iupItem) function,
update to support both V5 and V6 IUP 3.11.2 & _PLAC Records, ellipsis … changed to tilde ~ for ANSI/UTF-8 independant encoding, and location data converted to UTF-8 when needed,
StrGetDisplayText() NEW in V3.3 and not Plugin Store V3.2, prevented matching _PLAC Records so adjusted OK, degree ° symbol adjusted for ANSI/UTF-8 encoding (see FH6 comments).
@V3.2: Revised new Address…Place sub-options that retain entries, and new Set Preferences Database Management tab.
@V3.1: Statistics speed-up for many Locations, fixed Delete key bug in Subs/Lat/Long fields, and four new Address…Place sub-options.
@V3.0: Force all dialogue backgrounds to white, and make editing Substitute, Latitude & Longitude more responsive.
@V2.9: Optionally detects previous manually adjusted Locations, allows Geocode Plot/Map All/Some to escape at Quota Warning.
@V2.8: New Manual statistics class of Location, revised Geocode Plot All button, new Geocode Plot Some button, revised Next... buttons.
@V2.7: Translate any delimited Country/State/Chapman/County Code into full name before submitting to Geocoder.
@V2.7: Use read("*all") and table.concat instead of string concatenation, save Plugin sticky settings more often.
@V2.6: Fixed 'Not enough memory' for big _nameindex.html, set quota warning on all Plot/Map buttons, FlgFile/FolderExists now use "lfs".
@V2.5: Corrects missing webpage links when no 'See also' section, adds warning about exceeding 2,500 plots per 24 hours.
@V2.4: Checks map files belong to chosen Individual, adds Region Biasing option, uses bell blob for newline
@V2.3: Improved Map options & GUI avoids Blue, default Zoom only set on Preferences tab, multi-plot sample Maps on Create Maps tab.
@V2.2: Redesigned GUI, colour coded tree lists & statistics, zoom set from Google Map, better map Markers, bug fixes & code revisions.
@V2.1: Loads fast, resizeable main GUI, trees replace dropdown lists, better tooltips, bigger Google Map pane, bug fixes & code revisions.
@V2.0: New GUI with tabs, progress bars, and embedded HTML Google Map, plus Google Geocoder API, bug fixes & code revisions.
@V1.1: Improved version with fhSleep(), Interface Font option, and Knowledge Base Help.
@V1.0: Initial version using HTML Application (HTA) for Google Maps Geocoder.
--]=]Source: Map-Life-Facts-11.fh_lua