Closed EricNepean closed 4 years ago
-- ***To Setup -- Start Script Editor, open a new (blank) file, copy and paste both parts into one Script Editor Document, compile (hammer symbol) and save. -- Best if you make "Scripts" folder somewhere in your Documents or Desktop -- This file is suitable to use as an application in Capture One Pro's Script Menu
-- *** Operation in Script Editor -- Open the compiled and saved document -- Open the Script Editor log window, and select the messages tab -- The user may elect to set defaults for enabling or disabling results in Notifications, TextEdit and Script Editor by setting the "enable" variables at beginning of the script -- The user may change the default amount of reporting by setting the "debugLogLevel" and "ResultsFileMaxDebug" variables at beginning of the script -- There is a GUI which allows the user to modify settings -- If you are having some issues, then set debugLogLevel to 3 and send me the results from Script Editors log window, or Text Edit.
use AppleScript version "2.5" use scripting additions
property LoqqingVersion : "L13.00"
global debugLogEnable, parent_name local enableReadPrefs, enableWritePrefs, Script_Title
on getDefaultPrefs()
global debugLogEnable
local GC
local dfGUI, dfinitLogLevel, dfPrefFile, dfLogFiles, dfMaxErrorExits, dfForceDefaults, dSfSettingCheck
local dfExcludedSubCollectionL, dfSearchlevel, dfResultsCollection, dfResultsProject, dfC1ProgressBar, dfFastWorkFlow
local dfCompleteAlert, dfResultsFile, dfResultsByLoq, dfResultsByClipboard, dfResultsByNotifications
local dfResultsByDialog, dfDialogTimeout, dfDialogPercent, dfScriptProgressBar, dfDebugLogLevel, ResultsFileMaxDebug
local dfClipboardMaxDebug, dfDebugNotifications, dfnotificationsMaxDebug
############ Start of User settable area ###
## ***** Values in this section are safe to change, within limits indicated. Support is likely but no commitment
## Default Settings for the Application, these can be changed with the GUI on each run of the script
set dfExcludedSubCollectionL to {} -- Collections in this list will not be searched
set dfSearchlevel to 32 -- (1..100) Reduce if you only want to search top level collections - not verified to 100
set dfResultsCollection to true -- (true/false) found images are stored in an album in dfResultsProject
set dfC1ProgressBar to true -- (true/false)
set dfFastWorkFlow to false -- True sets a faster workflow with less user interaction
## Choice Lists for the GUI
set GC to {ExcludedSubCollectionChoice:{}} --
## Default Settings for reporting results, can be changed with the GUI on each run of the script
set dfCompleteAlert to false -- (true/false) - Create an Alert when the script completes
set dfResultsFile to true -- (true/false) - Log Results to a .txtx file opened in Text Editor
set dfResultsByLoq to true -- (true/false) - Log Results to Script Editor or Scripot Debugger
set dfResultsByClipboard to false -- (true/false) - Clipboard is populated only when the script exits
set dfResultsByDialog to false -- (true/false) - Results reported by Dialog
set dfDialogTimeout to 20 -- 1...119 seconds
set dfDialogPercent to 20 -- 1...100 Percent of screen height used by the Scripts Dialog
set dfResultsByNotifications to false -- (true/false) - only enable this if needed as it is slow
set dfScriptProgressBar to true -- (true/false) - enable a progress bar in the Script window
set dfDebugLogLevel to 0 -- 0...6 Values >0 result in increasing amounts of debug data and longer run times
set dfResultsFileMaxDebug to 2 -- 0...6 suggest not more than 2 if run from Script Editor
set dfClipboardMaxDebug to 0 -- 0...6 suggest not more than 4
set dfDebugNotifications to false -- (true/false) - enable notifications of errors and exceptions
set dfnotificationsMaxDebug to 0 -- 0...2 suggest not more than 1
### Script Settings - these cannot be adjusted by the GUI
set dfinitLogLevel to 3 -- logging level while script is initialising -- normally 0, set to 2 if problems
set dfPrefFile to true -- if false prevents creation of a .plist file for reading and writing of settings
set dfLogFiles to true -- if false prevents creation of .txt file for logging results
set dfMaxErrorExits to 6 -- after this number of script error exits, the Loqqing and Application settings are set to default
set dfForceDefaults to false -- Force the script to use the scripts default settings (above) instead of the settings in the .plist file
set dSfSettingCheck to false -- Force the script to always validate the settings, Enable this if you are having errors
set dfResultsProject to "ScriptSearchResults" -- this is the project where found images found by scripts are stored. Not possible to exclude this from searching
############ End of User Settable area ###
## Definitions for debugLogLevel which is used to define the level of reporting and logging
## -1 Results
## 0 Critical errors
## 1 Verbose results
## 2 Managed Exceptions (a problem occurred, but the script handled it)
## 3 Actions and Results
## 4 Simple diagnostics
## 5 Detailed diagnostics
## 6 Very long diagnostics
##
## Only levels ≤ debugLogLevel are reported
## Levels -1 and 0 are always reported
##
local AD, LD
set AD to {ExcludedSubCollectionL:dfExcludedSubCollectionL, maxSearchlevel:dfSearchlevel, enableResultsCollection:dfResultsCollection, nameResultsProject:dfResultsProject, enableC1ProgressBar:dfC1ProgressBar}
set LD to {enableResultsFile:dfResultsFile, enableResultsByLoq:dfResultsByLoq, enableResultsByClipboard:dfResultsByClipboard, enableResultsByNotifications:dfResultsByNotifications}
set LD to LD & {enableResultsByDialog:dfResultsByDialog, dialogTimeout:dfDialogTimeout, maxDialogPercent:dfDialogPercent}
set LD to LD & {enableScriptProgressBar:dfScriptProgressBar, enableCompleteAlert:dfCompleteAlert, debugLogLevel:dfDebugLogLevel, ResultsFileMaxDebug:dfResultsFileMaxDebug}
set LD to LD & {clipboardMaxDebug:dfClipboardMaxDebug, enableDebugNotifications:dfDebugNotifications, notificationsMaxDebug:dfnotificationsMaxDebug, enableFastGui:dfFastWorkFlow}
return {appDefs:AD, logDefs:LD, guiChoices:GC, initLogLevel:dfinitLogLevel, enablePrefFile:dfPrefFile, enableLogFiles:dfLogFiles, maxErrorExits:dfMaxErrorExits, forceScriptDefaults:dfForceDefaults, forceSettingsCheck:dSfSettingCheck}
end getDefaultPrefs
############ Start of execution flow
local appDefaults, logDefaults, initLogLevel, enablePrefFile, enableLogFiles, maxErrorExits, forceAppDefaults, forceSettingsCheck
tell getDefaultPrefs() --- this is called several times in different places to get other defaults as needed set {initLogLevel, enablePrefFile, enableLogFiles, maxErrorExits, forceAppDefaults, forceSettingsCheck} to ¬ {its initLogLevel, its enablePrefFile, its enableLogFiles, its maxErrorExits, its forceScriptDefaults, its forceSettingsCheck} end tell
if enablePrefFile then set {enableReadPrefs, enableWritePrefs} to {(not forceAppDefaults), true} else set {enableReadPrefs, enableWritePrefs} to {false, false} end if
if enableLogFiles or enablePrefFile then tell application "Finder" to get count of windows if enableLogFiles then tell application "TextEdit" to get count of windows tell application "System Events" to set parent_name to name of current application
set Script_Title to my getScriptTitle((path to me), true, false)
set {CoScriptLogsPath, CoScriptPrefsPath} to my setup4CaptureOneScript(initLogLevel, enableLogFiles, enablePrefFile)'s {path2CoLogFolder, path2CoPrefFolder} set guiData to my initGuiGlobals() -- anchor guiData here, as it refers to other variables also defined in this scope. Implicitly global, but actually is passed by reference. set mainLoqqingVersion to my initLoqqingGlobals(initLogLevel, Script_Title, CoScriptPrefsPath, enableLogFiles, CoScriptLogsPath, enableReadPrefs, enableWritePrefs)
my initPrefsFromFile(Script_Title, initLogLevel, maxErrorExits, enableReadPrefs, enableWritePrefs, CoScriptPrefsPath, enableLogFiles, CoScriptLogsPath, mainLoqqingVersion, forceSettingsCheck)
setLoqPrefs(forceAppDefaults, enableReadPrefs, enableWritePrefs, enableLogFiles) my loqThis(1, false, ("Started from: " & parent_name & " Action: Find Offline Image files"))
local coParList, coAppName, COPDocRef, minCOPversion, maxCOPversion
set {minCOPversion, maxCOPversion, coParList} to {"12", "13", {}}
tell my validateCOP5(minCOPversion, maxCOPversion) if its hasErrors then error (get its errorText) set {coAppName, COPDocRef} to {its theAppName, its COPDocRef} set coParList to coParList & {coAppName:its theAppName, copVersion:its copVersion, COPDocRef:its COPDocRef} end tell
tell my validateCOPdoc5(COPDocRef, {"Catalog"}) if its hasErrors then error (get its errorText) set coParList to coParList & {COPDocName:its COPDocName, COPDocKind_s:its COPDocKind_s} end tell
tell my validateCOcollection6(COPDocRef) if its hasErrors then error (get its errorText) set coParList to coParList & {selectedCollectionRef:its selectedCollectionRef, kindSelectedCollection_s:its selectedCollectionKind_s, selectedCollectionName:its selectedCollectionName, selectedCollAllImages:its selectedCollectionIsAllImages} end tell
setAppPrefs(forceAppDefaults, coParList)
local userCancelledGUI, perfMetrics
set userCancelledGUI to false
makeGuiRecords(guiData) my loqGUIsettings2(guiData) my guisGUiSettings(guiData)
tell application "System Events" to set frontmost of process parent_name to true
set userCancelledGUI to my guiRunSettingsEditor(guiData)
if userCancelledGUI then guiCancelResetDefs() -- if the user cancelled the script, ask if the script preferences should be reset to default set perfMetrics to "User Cancelled" else my saveLoqqingPrefs() saveAppPrefs() set perfMetrics to searchHandler() -- does the search end if
return my loqqedNormalHalt7(perfMetrics)
tell application "System Events" to set frontmost of process coAppName to true if Loqqing's enableResultsFile then tell application "System Events" to set frontmost of process "TextEdit" to true else tell application "System Events" to set frontmost of process parent_name to true end if false
################ The End ###############
on searchHandler() global debugLogEnable, theApp, Loqqing, C1
local Mark1, Mark2, Mark3, Mark4, Mark5
set Mark1 to my GetTick_Now()
setSearchWindows()
## Setup the search
local thisCollectionRef, countProcessedImages, thisColl_name, thisCollKindS, countImages, estDuration, estProgressDuration_S, nextSearchLevel
tell application "Capture One 20"
set thisCollectionRef to C1's selectedCollectionRef
tell thisCollectionRef
set thisColl_name to its name
set thisCollKindS to my convertKindList((get its kind))
set countImages to count of images
end tell
end tell
set nextSearchLevel to 1
if ({"project", "album", "smart album"} does not contain thisCollKindS) then ¬
set countImages to countAllImages(thisCollectionRef, thisColl_name, thisCollKindS, nextSearchLevel)
my loqThis(1, false, ("Starting in " & thisCollKindS & " \"" & thisColl_name & "\" with " & countImages & " Images"))
set {estDuration, estProgressDuration_S} to {0, ""}
if 0 < (0 + (theApp's ratePerSecond)) then
set estDuration to (countImages / (theApp's ratePerSecond))
set estProgressDuration_S to " (" & (my roundDecimals(estDuration, 0)) & "s)"
my loqThis(1, false, ("Estimated Execution Time is " & (my roundDecimals(estDuration, 0)) & " seconds"))
end if
set Mark2 to my GetTick_Now()
if 300 < estDuration then
tell application "System Events" to set frontmost of process parent_name to true
set dialog_result to display dialog "Estimated Execution time is " & estProgressDuration_S with title Loqqing's Script_Title ¬
buttons {"Exit Script", "Continue"} default button "Continue"
if (get button returned of dialog_result) contains "Exit" then
my loqThis(-1, false, ("User Cancelled Script on predicted execution time of " & estProgressDuration_S))
guiCancelResetDefs()
return
end if
setSearchWindows()
else if (0 = estDuration) and (countImages > 5000) then
tell application "System Events" to set frontmost of process parent_name to true
set dialog_result to display dialog C1's kindSelectedCollection_s & " contains " & countImages & " images" & estProgressDuration_S with title Loqqing's Script_Title ¬
buttons {"Exit Script", "Continue"} default button "Continue"
if (get button returned of dialog_result) contains "Exit" then
my loqThis(-1, false, ("User Cancelled Script on too many images: " & countImages))
guiCancelResetDefs()
return
end if
setSearchWindows()
end if
set Mark5 to my GetTick_Now()
local Result_AlbumRoot, Result_ProjectName, Coll_Init_Text, ref2ResultAlbum
if theApp's enableResultsCollection then
set Result_AlbumRoot to "OfflineFiles"
set Result_ProjectName to (get theApp's nameResultsProject)
set Coll_Init_Text to "Images with offline files will be added to album "
set ref2ResultAlbum to my InitializeResultsCollection(Result_ProjectName, Result_AlbumRoot, Coll_Init_Text)
set C1 to {ref2ResultAlbum:ref2ResultAlbum} & C1
end if
set Mark3 to my GetTick_Now()
local nextSearchLevel, countImageNotFound, Coll_path
set nextSearchLevel to 1
set countImageNotFound to 0
set countProcessedImages to 0
set Coll_path to ">" & thisColl_name
tell my search_collection(thisCollectionRef, thisColl_name, thisCollKindS, nextSearchLevel, Coll_path, true, estProgressDuration_S)
set countImageNotFound to its countImageNotFound
set countProcessedImages to its countProcessedImages
end tell
set Mark4 to my GetTick_Now()
local elapsedTime1, elapsedTime1s, elapsedTime2s, searchTimePerVariant, searchTimeMsPerVariant, countTimeMsPerVariant
my loqThis(-1, false, (return & "Found " & countImageNotFound & " of " & countProcessedImages & " images with offline files in " & thisCollKindS & " \"" & thisColl_name & "\" (" & countImages & " images)"))
set {countTimeMsPerVariant, searchTimeMsPerVariant} to {"--", "--"}
set elapsedTime2s to my roundDecimals(Mark4 - Mark3, 3)
if (countProcessedImages > 10) and (0.1 < (Mark4 - Mark3)) then -- reasonable accuracy
set theApp's ratePerSecond to countProcessedImages / (Mark4 - Mark3) -- ratePerSecond is used only after setup is completed
saveAppPrefs()
set searchTimeMsPerVariant to my roundDecimals(((Mark4 - Mark3) / countProcessedImages * 1000), 1)
end if
set elapsedTime1 to (Mark2 - Mark1) + (Mark3 - Mark5)
set elapsedTime1s to my roundDecimals(elapsedTime1, 3)
if (countImages > 10) and (0.1 < elapsedTime1) then ¬
set countTimeMsPerVariant to my roundDecimals((elapsedTime1 / countImages * 1000), 1) -- reasonable accuracy
return (return & "Searching: " & elapsedTime2s & "s; " & searchTimeMsPerVariant & "ms per image Setup: " & elapsedTime1s & "s; " & countTimeMsPerVariant & "ms per image")
end searchHandler
on setSearchWindows() global debugLogEnable, theApp, Loqqing, C1, parent_name
tell application "System Events" -- Arrange the windows to show results and progress bars on top
set theAppName to (get C1's coAppName as text)
set frontmost of process theAppName to true
if (not theApp's enableC1ProgressBar) and Loqqing's enableScriptProgressBar and (parent_name = "Script Editor") then ¬
set frontmost of process "Script Editor" to true
if Loqqing's enableResultsFile then set frontmost of process "TextEdit" to true
end tell
end setSearchWindows
on countAllImages(thisCollection, thisColl_name, thisCollKindS, searchLevel) -- recursive handler to count images in a collection and it's subcollections
global debugLogEnable, theApp, Loqqing, C1
if theApp's ExcludedSubCollectionL contains thisColl_name then return 0
local countImages
tell application "Capture One 20" to tell thisCollection to set countImages to count of every image
if debugLogEnable then
set actionString to "counting images in " & thisCollKindS & " \"" & thisColl_name & "\""
my loqThis(3, false, actionString)
end if
local nameSubCollsL, refSubCollsL, kindSubCollsSL, nextSearchLevel, countSubColls
local nextCollName, nextCollRef, nextCollKindS, ptrSubColl
if ({"project", "album", "smart album"} contains thisCollKindS) or (searchLevel ≥ theApp's maxSearchlevel) then return countImages
tell application "Capture One 20" to tell thisCollection to tell every collection to ¬
set {refSubCollsL, nameSubCollsL, kindSubCollsSL} to {(get it), (get its name), (my convertKindList(get its kind))}
set {nextSearchLevel, countSubColls} to {(searchLevel + 1), (count of nameSubCollsL)}
if (countSubColls > 0) then
repeat with ptrSubColl from 1 to countSubColls
set {nextCollName, nextCollRef, nextCollKindS} to {(nameSubCollsL's item ptrSubColl), (refSubCollsL's item ptrSubColl), (kindSubCollsSL's item ptrSubColl)}
set countImages to countImages + (my countAllImages(nextCollRef, nextCollName, nextCollKindS, nextSearchLevel))
end repeat
end if
return countImages
end countAllImages
on search_collection(thisCollection, thisColl_name, thisCollKindS, searchLevel, thisCollPath, printColl, estProgressDuration_S) -- recursive handler to search a collection and it's subcollections
global debugLogEnable, theApp, Loqqing, C1
local imagePathL, nextSearchLevel, countImages, actionString, enableProgressBar, useC1ProgressBar, secondsThresh
if theApp's ExcludedSubCollectionL contains thisColl_name then return {countImageNotFound:0, countProcessedImages:0, _debug:(get my loqThis(1, false, ("Skipped " & thisColl_name)))}
tell application "Capture One 20" to tell thisCollection to set countImages to count of every image
set actionString to "Searching " & thisCollKindS & " \"" & thisColl_name & "\" with " & countImages & " images" & estProgressDuration_S
if debugLogEnable then my loqThis(3, false, actionString)
set {secondsThresh, enableProgressBar} to {3, false}
if (Loqqing's enableScriptProgressBar or theApp's enableC1ProgressBar) and ¬
(C1's selectedCollAllImages or ¬
((0 ≠ theApp's ratePerSecond) and (countImages > (secondsThresh * (theApp's ratePerSecond)))) or ¬
((0 = theApp's ratePerSecond) and (countImages > 500)) ¬
) then
set {enableProgressBar, progressInterval, useC1ProgressBar} to {true, 25, (true and theApp's enableC1ProgressBar)} -- updating the CO progress counter takes as long as checking the path of one image
if useC1ProgressBar then
tell application "Capture One 20" to set {progress text, progress completed units, progress total units, progress additional text} to {actionString, 0, countImages, ("" & countImages & " images")}
else
set {progress description, progress total steps, progress completed steps} to {actionString, countImages, 0}
end if
end if
tell application "Capture One 20" to tell thisCollection to set imagePathL to get path of every image
local loc_Text, first_Hit, countImageNotFound, countProcessedImages, ImageCounter, imagepath, imageName, progressInterval
set loc_Text to "In " & thisCollKindS & " " & thisCollPath & ":"
set first_Hit to true and not printColl -- if this is the starting collection then don't print out the collection name
set countImageNotFound to 0
set countProcessedImages to count of imagePathL
if 0 < countImages then
repeat with ImageCounter from 1 to countImages
if enableProgressBar and (0 = ImageCounter mod progressInterval) then
if useC1ProgressBar then
tell application "Capture One 20" to set progress completed units to ImageCounter
else
set progress completed steps to ImageCounter
end if
end if
set imagepath to item ImageCounter of imagePathL
tell application "System Events"
set hasImageFile to (get exists file imagepath)
if not hasImageFile then
set countImageNotFound to countImageNotFound + 1
if first_Hit then my loqThis(-1, false, (return & loc_Text))
set first_Hit to false
tell application "Capture One 20" to tell C1's COPDocRef to tell thisCollection to tell image ImageCounter
set imageName to name
if theApp's enableResultsCollection then add inside (C1's ref2ResultAlbum) variants (get variants)
end tell
my loqThis(-1, false, ("File for " & imageName & " not found at " & imagepath))
end if
end tell
end repeat
if enableProgressBar then tell application "Capture One 20" to set progress completed units to countImages
if Loqqing's enableScriptProgressBar then set progress completed steps to countImages
end if
if debugLogEnable then my loqThis(3, false, ("Done " & thisCollKindS & " " & thisCollPath & " with " & countImageNotFound & " offline files"))
local nameSubCollsL, refSubCollsL, kindSubCollsSL, nextSearchLevel, countSubColls
local nextCollName, nextCollRef, nextCollKindS, ptrSubColl
if thisCollKindS ≠ "project" then -- do not search collections contained inside a project to avoid repeated "hits" of the same image
tell application "Capture One 20" to tell C1's COPDocRef to tell thisCollection to tell every collection to ¬
set {refSubCollsL, nameSubCollsL, kindSubCollsSL} to {(get it), (get name of it), (my convertKindList(kind))}
set {nextSearchLevel, countSubColls} to {(searchLevel + 1), (count of nameSubCollsL)}
if (countSubColls > 0) and (nextSearchLevel ≤ theApp's maxSearchlevel) then
repeat with ptrSubColl from 1 to countSubColls
set {nextCollName, nextCollRef, nextCollKindS} to {(get nameSubCollsL's item ptrSubColl), (get refSubCollsL's item ptrSubColl), (get kindSubCollsSL's item ptrSubColl)}
set nextCollPath to thisCollPath & ">" & nextCollName
tell my search_collection(nextCollRef, nextCollName, nextCollKindS, nextSearchLevel, nextCollPath, false, estProgressDuration_S)
set countProcessedImages to countProcessedImages + (its countProcessedImages)
set countImageNotFound to countImageNotFound + (its countImageNotFound)
end tell
end repeat
end if
end if
return {countImageNotFound:countImageNotFound, countProcessedImages:countProcessedImages}
end search_collection
on setLoqPrefs(forceDefaults, enableReadPrefs, enableWritePrefs, enableLogFiles) global debugLogEnable, Loqqing
local logDefs, saveLoqPrefs
tell getDefaultPrefs() to set logDefs to its logDefs
set logDefs to {fullSetup:true, isChecked:false} & logDefs
set saveLoqPrefs to false
if (not Loqqing's fullSetup) or forceDefaults then -- setup Loqqing from scripts defaults
set {Loqqing, saveLoqPrefs} to {logDefs & Loqqing, true} -- save the revised values, isChecked is false
my loqThis(1, false, "Loqqing settings were set to Script Defaults")
end if
tell Loqqing -- control what's useable for this script
set its gateResultsByClipboard to true
set its gateResultsFile to true
set its gateResultsDialog to true
set its gateResultsNotification to true
set its gateParentLoqqing to true
set its enableReadPrefs to enableReadPrefs
set its enableWritePrefs to enableWritePrefs
end tell
if not enableLogFiles then set Loqqing's gateResultsFile to false
if saveLoqPrefs then
set loqResultMethod to my setupLoqqing6()
if enableWritePrefs then my saveLoqqingPrefs()
end if
end setLoqPrefs
on setAppPrefs(forceDefaults, coParList)
global debugLogEnable, Loqqing
global theApp, guiChoices, C1
local appSettingsName, selectedCollectionRef, kindSelectedCollection_s, nameSelectedCollection, selectedCollAllImages
set theAppSettingsName to "appSettings"
copy ({appSettingsName:theAppSettingsName} & coParList) to C1
local appDefs, saveAppPrefs, gotAppPrefs, AppPrefs, theErrMess, nameSubColl_L
tell getDefaultPrefs() to set {appDefs, guiChoices} to {its appDefs, its guiChoices}
set {saveAppPrefs, gotAppPrefs} to {(true and Loqqing's enableWritePrefs), false}
if Loqqing's enableReadPrefs and not forceDefaults then
try
tell application "System Events" to tell property list file (Loqqing's posix2PrefFile)
if (get name of every property list item) contains theAppSettingsName then
set AppPrefs to (get value of property list item theAppSettingsName) & {}
set gotAppPrefs to true
end if
end tell
if gotAppPrefs then
if debugLogEnable then my loqThis(2, false, "Application Settings loaded from the Preference File")
copy (AppPrefs & appDefs) to theApp -- AppPrefs takes priority
if AppPrefs = theApp then set saveAppPrefs to false -- don't save needlessly
else
my loqThis(0, true, "Did not find Application Settings in the Preference File")
set gotAppPrefs to false
end if
on error errmess
set theErrMess to "Error Message: " & errmess
if debugLogEnable then my loqThis(0, true, "Failed Reading Application Settings from Preference File \"" & theAppSettingsName & "\" with Error Message " & (get errmess))
set gotAppPrefs to false
end try
end if
local forceAppDefaults, enableReadPrefs, enableWritePrefs, enableLogFiles
if not gotAppPrefs then
copy (appDefs & {ratePerSecond:0}) to theApp
tell Loqqing to set {forceAppDefaults, enableReadPrefs, enableWritePrefs, enableLogFiles} to ¬
{true, (true and its enableReadPrefs), (true and its enableWritePrefs), (true and its gateResultsFile)}
setLoqPrefs(forceAppDefaults, enableReadPrefs, enableWritePrefs, enableLogFiles)
my loqThis(1, false, "Application settings were set to Script Defaults")
end if
if saveAppPrefs then saveAppPrefs()
local nameSubColl_L, nameSubSubColl_L, aList
if C1's selectedCollAllImages or ("project" = C1's kindSelectedCollection_s) then
set guiChoices's ExcludedSubCollectionChoice to {}
else
tell application "Capture One 20" to tell C1's selectedCollectionRef
set nameSubColl_L to (get name of its collections)
set nameSubSubColl_L to get name of collections of (collections whose kind is not project)
end tell
repeat with aList in nameSubSubColl_L
set nameSubColl_L to nameSubColl_L & aList
end repeat
set guiChoices's ExcludedSubCollectionChoice to nameSubColl_L & guiChoices's ExcludedSubCollectionChoice
end if
end setAppPrefs
on saveAppPrefs()
global debugLogEnable, theApp, Loqqing, C1
local theAppSettingsName
set theAppSettingsName to (get "" & C1's appSettingsName)
if Loqqing's enableWritePrefs or (false ≠ Loqqing's posix2PrefFile) then
tell application "System Events" to tell property list file (Loqqing's posix2PrefFile)
if (get name of every property list item) contains theAppSettingsName then
set value of property list item theAppSettingsName to theApp
else
make new property list item at end with properties {kind:record, name:theAppSettingsName, value:theApp}
end if
end tell
if debugLogEnable then my loqThis(2, false, "Saved Application Settings into .plist File")
else
my loqThis(1, false, "Unable to Save Application Set Up into Preference File - no path")
end if
end saveAppPrefs
on guiCancelResetDefs() global debugLogEnable, Loqqing, parent_name, C1 local forceAppDefaults, enableReadPrefs, enableWritePrefs, enableLogFiles, newC1
set prefSaved to ""
copy (get {} & C1) to newC1
if Loqqing's enableFastGui then
set prefSaved to "; changed settings were not saved"
else
tell application "System Events" to set frontmost of process parent_name to true
set dialog_result to display dialog "Reset the Application Settings to Default?" with title "Settings for " & Loqqing's Script_Title ¬
buttons {"Exit Script", "Reset and Exit"} default button "Exit Script"
if (get button returned of dialog_result) contains "Reset" then
set prefSaved to "; script settings were reset"
if Loqqing's enableWritePrefs then set prefSaved to " and saved"
setAppPrefs(true, newC1)
tell Loqqing to set {forceAppDefaults, enableReadPrefs, enableWritePrefs, enableLogFiles} to ¬
{true, (true and its enableReadPrefs), (true and its enableWritePrefs), (true and its gateResultsFile)}
setLoqPrefs(forceAppDefaults, enableReadPrefs, enableWritePrefs, enableLogFiles)
else
if Loqqing's enableWritePrefs then set prefSaved to "; changed settings were saved"
saveAppPrefs()
my saveLoqqingPrefs()
end if
end if
my loqThis(-1, false, "User has cancelled the search" & prefSaved)
return
end guiCancelResetDefs
on makeGuiRecords(theGuiData)
global debugLogEnable, theApp, guiChoices, C1
local settingList, scriptList, loqSettings_L, loqScript_L
local helpMaxSearchLevel
set helpMaxSearchLevel to "The maximum number of levels under the selected colllection which will be searched"
set settingList to {}
set end of settingList to {s_ID:0, s_Name:"Max Search Level", s_Help:helpMaxSearchLevel, s_Value:(a reference to theApp's maxSearchlevel), s_UserSet:true, s_Active:C1's selectedCollAllImages, s_Invert:true, s_Class:"Integer", s_LType:"InMin&InMax", s_Limit_L:{1, 100}}
set end of settingList to {s_ID:0, s_Name:"Excluded Collections", s_Value:(a reference to theApp's ExcludedSubCollectionL), s_UserSet:true, s_Active:C1's selectedCollAllImages, s_Invert:true, s_Class:"List_Text", s_LType:"Free&List", s_Limit_L:(guiChoices's ExcludedSubCollectionChoice)}
set end of settingList to {s_ID:0, s_Name:"Enable Results Collection", s_Value:(a reference to theApp's enableResultsCollection), s_UserSet:true, s_Active:true, s_Class:"Boolean"}
set end of settingList to {s_ID:0, s_Name:"Enable C1 Progress Bar", s_Value:(a reference to theApp's enableC1ProgressBar), s_UserSet:true, s_Active:true, s_Class:"Boolean"}
set end of settingList to {s_ID:0, s_Name:"++++", s_Value:missing value, s_UserSet:missing value, s_Active:true}
set theGuiData's guiRecordList to settingList & theGuiData's guiRecordList
return
end makeGuiRecords
on finalCleanup()
global debugLogEnable, imagePathL, C1, theApp, loqDialogTextList, loqClipboardTextS, Loqqing, parent_name
if debugLogEnable then my loqThis(3, false, "Final Cleanup ")
if parent_name does not contain "Script" then return
if theApp's enableC1ProgressBar then tell application "Capture One 20" to set {progress text, progress completed units, progress total units, progress additional text} to {"", 1, 0, ""}
set {imagePathL, C1, theApp, loqDialogTextList, loqClipboardTextS} to {null, null, null, null, null}
end finalCleanup
on setup4CaptureOneScript(debugLogLevel, gateReportFiles, gatePrefFiles)
local alias2PrefsParent_as, alias2PrefsParent_a, alias2ReportsParent_a, alias2CoScriptPrefs, alias2CoScriptReports
set {alias2CoScriptReports, alias2CoScriptPrefs} to {false, false}
set alias2PrefsParent_as to (((get path to scripts folder) as text) & "Capture One Scripts:")
if 1 ≤ debugLogLevel then tell me to log "Finding Reports and Prefs parent folder \"" & (get POSIX path of alias2PrefsParent_as) & "\""
try
set alias2PrefsParent_a to alias alias2PrefsParent_as
set alias2ReportsParent_a to alias2PrefsParent_a
on error errorString number errorNumber
error "setup4CaptureOneScript() had error " & errorNumber & " \"" & errorString & "\"."
end try
if gatePrefFiles then set alias2CoScriptPrefs to my findTargetFolder(alias2PrefsParent_a, "Script Preferences", debugLogLevel, true)
if gateReportFiles then set alias2CoScriptReports to my findTargetFolder(alias2ReportsParent_a, "Script Reports", debugLogLevel, true)
return {path2CoLogFolder:(alias2CoScriptReports as text), path2CoPrefFolder:(alias2CoScriptPrefs as text)}
end setup4CaptureOneScript
on validateCOP5(minCOPversionstr, maxCOPversionstr)
## General purpose initialisation handler for scripts using Capture One Pro
## Extract and check basic information about the Capture One application
global debugLogEnable
local theAppName, copVersion, copVersionStr, copDetailedVersion
local minVersionPass, maxVersionPass
tell application "Capture One 20" to set {theAppName, copVersionStr, copDetailedVersion, COPDocRef} to {name, app version, version, current document}
set copVersion to (word -1 of copVersionStr)
set theAppName to (get ("" & theAppName) as text)
if debugLogEnable then my loqThis(1, false, ("Using " & theAppName & " version " & copDetailedVersion))
tell my compareVersion(copVersion, minCOPversionstr, maxCOPversionstr) to set {minVersionPass, maxVersionPass} to {its minVersionPass, its maxVersionPass}
if not minVersionPass then return {hasErrors:true, errorText:(get my loqqed_Error_Halt5(("This Script does not support version " & copDetailedVersion & " of Capture One - versions " & minCOPversionstr & " and later are supported")))}
if not maxVersionPass then my loqThis(0, true, ("Caution: This Script has not been verified for Capture One " & copDetailedVersion))
return {hasErrors:false, theAppName:theAppName, copVersion:copVersion, COPDocRef:COPDocRef}
end validateCOP5
on validateCOPdoc5(theDocRef, validDocKindList)
## General purpose initialisation handler for scripts using Capture One Pro
## Extract and check basic information about a document
global debugLogEnable
--local COPDocKind_s, COPDocKind_p, COPDocName
if "text" = (get class of theDocRef as text) and (0 = (get count of theDocRef)) then tell application "Capture One 20" to set theDocRef to get current document
try
tell application "Capture One 20" to set {COPDocName, COPDocKind_p} to get {name, kind} of theDocRef
on error errorText number errorNumber
return {hasErrors:true, errorText:(get my loqqed_Error_Halt5("The Script could not retrieve Capture One document info. Error " & errorNumber & ": \"" & errorText & "\""))}
end try
set COPDocKind_s to convertKindList(COPDocKind_p)
if validDocKindList does not contain COPDocKind_s then return {hasErrors:true, errorText:(get my loqqed_Error_Halt5((COPDocName & " is a " & COPDocKind_s & " -- not supported by this script")))}
return {hasErrors:false, COPDocName:COPDocName, COPDocKind_s:COPDocKind_s}
end validateCOPdoc5
on validateCOPcollections5(theDocRef)
## General purpose initialisation handler for scripts using Capture One Pro
## Extract basic information regarding the current collection, and the top level collections
global debugLogEnable
local selectedCollectionRef, selectedCollectionIndex, countTopCollections
local nameSelectedCollection, kindSelectedCollection_s, userSelectedCollection, idSelectedCollection
local namesTopCollections, kindsTopCollections_s, usersTopCollections, idsTopCollections
tell application "Capture One 20" to set {COPDocName, COPDocKind_p} to get {name, kind} of theDocRef
set COPDocKind_s to convertKindList(COPDocKind_p)
tell application "Capture One 20" to tell theDocRef
set selectedCollectionRef to get current collection
if (missing value = selectedCollectionRef) then
try
set current collection to collection "All Images"
on error
set current collection to first collection
end try
set selectedCollectionRef to get current collection
end if
tell selectedCollectionRef to set {nameSelectedCollection, kindSelectedCollection_s, userSelectedCollection, idSelectedCollection} to {name, my convertKindList(kind), user, id}
tell every collection to set {namesTopCollections, kindsTopCollections_s, usersTopCollections, idsTopCollections} to {name, my convertKindList(kind), user, id}
end tell
set countTopCollections to count of namesTopCollections
set selectedCollectionIndex to 0
repeat with collectionCounter from countTopCollections to 1 by -1
if (idSelectedCollection = item collectionCounter of idsTopCollections) then
set selectedCollectionIndex to collectionCounter
exit repeat
end if
end repeat
local selectedCollectionMirroredAtTopLast, bottomUserCollectionIndex, topUserCollectionIndex, selectedCollectionIsUser
set {bottomUserCollectionIndex, topUserCollectionIndex} to {0, 0}
local foldersTopCollection, folderSelectedCollection, countFavoriteCollections, namesFavoriteCollections
if COPDocKind_s = "catalog" then
set selectedCollectionIsUser to userSelectedCollection
set selectedCollectionMirroredAtTopLast to ¬
(selectedCollectionIndex = countTopCollections) and userSelectedCollection and ¬
({"catalog folder", "favorite"} does not contain last item of kindsTopCollections_s)
repeat with collectionCounter from 1 to topUserCollectionIndex
if (get item collectionCounter of usersTopCollections) then
set bottomUserCollectionIndex to collectionCounter + 0
exit repeat
end if
end repeat
if bottomUserCollectionIndex > 0 then
repeat with collectionCounter from bottomUserCollectionIndex to countTopCollections
if not (get item collectionCounter of usersTopCollections) then
set topUserCollectionIndex to collectionCounter - 1
exit repeat
end if
end repeat
end if
set {countFavoriteCollections, namesFavoriteCollections} to {missing value, missing value}
else if COPDocKind_s = "session" then
tell application "Capture One 20" to tell theDocRef
set foldersTopCollection to folder of every collection
set folderSelectedCollection to folder of selectedCollectionRef
end tell
set selectedCollectionIsUser to userSelectedCollection and (missing value = folderSelectedCollection)
repeat with collectionCounter from 1 to countTopCollections
if (get item collectionCounter of userTopCollections) and (missing value = item collectionCounter of foldersTopCollection) then
set bottomUserCollectionIndex to collectionCounter + 0
exit repeat
end if
end repeat
if bottomUserCollectionIndex > 0 then
repeat with collectionCounter from bottomUserCollectionIndex to countTopCollections
if not ((get item collectionCounter of userTopCollections) and (missing value = item collectionCounter of foldersTopCollection)) then
set topUserCollectionIndex to collectionCounter - 1
exit repeat
end if
end repeat
end if
set countFavoriteCollections to countTopCollections - topUserCollectionIndex
if 1 > countFavoriteCollections then
set namesFavoriteCollections to {}
else
set namesFavoriteCollections to (get items (topUserCollectionIndex + 1) thru countTopCollections of namesTopCollections)
end if
set selectedCollectionMirroredAtTopLast to false
end if
local selectedCollectionIsUser, namesTopUserCollections, kindsTopUserCollections_s, countTopUserCollections
if (topUserCollectionIndex < bottomUserCollectionIndex) or (0 = topUserCollectionIndex) then
set {topUserCollectionIndex, bottomUserCollectionIndex} to {missing value, missing value}
set {namesTopUserCollections, kindsTopUserCollections_s, countTopUserCollections} to {{}, {}, 0}
else
set {namesTopUserCollections, kindsTopUserCollections_s} to {(get items bottomUserCollectionIndex thru topUserCollectionIndex of namesTopCollections), (get items bottomUserCollectionIndex thru topUserCollectionIndex of kindsTopCollections_s)}
set countTopUserCollections to count of namesTopUserCollections
end if
return {hasErrors:false, namesTopUserCollections:namesTopUserCollections, kindsTopUserCollections_s:kindsTopUserCollections_s, countTopUserCollections:countTopUserCollections, selectedCollectionRef:selectedCollectionRef, selectedCollectionIndex:selectedCollectionIndex, kindSelectedCollection_s:kindSelectedCollection_s, nameSelectedCollection:nameSelectedCollection, selectedCollectionMirroredAtTopLast:selectedCollectionMirroredAtTopLast, selectedCollectionIsUser:selectedCollectionIsUser, bottomUserCollectionIndex:bottomUserCollectionIndex, topUserCollectionIndex:topUserCollectionIndex, countFavoriteCollections:countFavoriteCollections, namesFavoriteCollections:namesFavoriteCollections}
end validateCOPcollections5
on validateCOcollection6(theDocRef)
## General purpose initialisation handler for scripts using Capture One Pro
## Extract basic information regarding the current collection
global debugLogEnable
local collectionRef, collectionName, collectionKind_s, collectionId, collectionUser, collectionIsMirrored, docKind_p, docKind_s, selectedCollectionIsUser, collectionIsAllImages
tell application "Capture One 20" to tell theDocRef
set docKind_p to kind
set collectionRef to get current collection
if (missing value = collectionRef) then
set current collection to first collection
set collectionRef to get current collection
if debugLogEnable then my loqThis(0, true, "No collection was selected - selected the first collection")
end if
tell collectionRef to set {collectionName, collectionKind_s, collectionId, collectionUser} to {name, my convertKindList(kind), id, user}
if debugLogEnable then my loqThis(2, false, ("Collection: Name-" & collectionName & "; Kind-" & collectionKind_s & "; ID-" & collectionId & "; User-" & collectionUser))
if (docKind_p = session) then
tell application "Capture One 20" to tell theDocRef to set selectedCollectionIsUser to ¬
collectionUser and (missing value = (get folder of collectionRef))
if debugLogEnable then my loqThis(2, false, ("Session Collection Is User: " & selectedCollectionIsUser))
set collectionIsMirrored to false
else if (docKind_p = catalog) then
set selectedCollectionIsUser to collectionUser
set collectionIsMirrored to collectionUser and (collectionId = (get id of last collection))
if debugLogEnable then my loqThis(2, false, ("Catalog Collection Is Mirrored at Top Last: " & collectionIsMirrored))
set collectionIsAllImages to (not collectionUser) and (collectionId = (get id of first collection))
else
tell application "Capture One 20" to set docKind_s to (docKind_p as text)
return {hasErrors:true, errorText:(get my loqqed_Error_Halt5("validateCOcollection6 received an unexpected Document Kind: " & docKind_s))}
end if
end tell
return {hasErrors:false, selectedCollectionRef:collectionRef, selectedCollectionKind_s:collectionKind_s, selectedCollectionName:collectionName, selectedCollectionMirroredAtTopLast:collectionIsMirrored, selectedCollectionIsUser:selectedCollectionIsUser, selectedCollectionID:collectionId, selectedCollectionIsAllImages:collectionIsAllImages}
end validateCOcollection6
on convertKindList(theKind)
## General Purpose Handler for scripts using Capture One Pro
## Capture One returns the chevron form of the "kind" property when AppleScript is run as an Application
## Unless care is taken to avoid text conversion of this property, this bug breaks script decisions based on "kind"
## This script converts text strings with the chevron form to strings with the expected text form
## The input may be a single string, a single enum, a list of strings or a list of enums
## The code is not compact but runs very fast, between 60us and 210us per item
local kind_sl, theItem, kindItem_s, code_start, kindItem_s, kind_code, kind_type
if list = (class of theKind) then
set kind_sl to {}
repeat with theItem in theKind
set the end of kind_sl to convertKindList(theItem)
end repeat
return kind_sl
else if text = (class of theKind) then
if "«" ≠ (get text 1 of theKind) then return theKind
set kindItem_s to theKind
else
tell application "Capture One 20" to set kindItem_s to (get theKind as text)
if "«" ≠ (get text 1 of kindItem_s) then return kindItem_s
end if
set code_start to -5
if ("»" ≠ (get text -1 of kindItem_s)) or (16 > (count of kindItem_s)) then ¬
error (get my loqqed_Error_Halt5("convertKindList received an unexpected Kind string: " & kindItem_s))
set kind_code to get (text code_start thru (code_start + 3) of kindItem_s)
set kind_type to get (text code_start thru (code_start + 1) of kindItem_s)
if kind_type = "CC" then ## Collection Kinds
if kind_code = "CCpj" then
return "project"
else if kind_code = "CCgp" then
return "group"
else if kind_code = "CCal" then
return "album"
else if kind_code = "CCsm" then
return "smart album"
else if kind_code = "CCfv" then
return "favorite"
else if kind_code = "CCff" then
return "catalog folder"
end if
else if kind_type = "CL" then ## Layer Kinds
if kind_code = "CLbg" then
return "background"
else if kind_code = "CLnm" then
return "adjustment"
else if kind_code = "CLcl" then
return "clone"
else if kind_code = "CLhl" then
return "heal"
end if
else if kind_type = "CR" then ## Watermark Kinds
if kind_code = "CRWn" then
return "none"
else if kind_code = "CRWt" then
return "textual"
else if kind_code = "CRWi" then
return "imagery"
end if
else if kind_type = "CO" then ## Document Kinds
if kind_code = "COct" then
return "catalog"
else if kind_code = "COsd" then
return "session"
end if
end if
error (get my loqqed_Error_Halt5("convertKindList received an unexpected Kind string: " & kindItem_s))
end convertKindList
on InitializeResultsCollection(nameResultProject, nameResultAlbumRoot, Coll_Init_Text)
## General Purpose Handler for scripts using Capture One Pro
## Sets up a project and albums for collecting images
global debugLogEnable, C1, Loqqing
local coll_ctr, nameResultAlbum, resultProjectList, Coll_Init_Text, ref2ResultAlbum
tell application "Capture One 20" to tell C1's COPDocRef
if 0 = (get count of (collections whose name is nameResultProject and user is true)) then
set ref2ResultProject to make new collection with properties {kind:project, name:nameResultProject}
if debugLogEnable then my loqThis(1, false, ("Created " & nameResultProject))
else
set resultProjectList to collections whose name is nameResultProject and kind is project
if 1 = (count of resultProjectList) then
set ref2ResultProject to first item of resultProjectList
if debugLogEnable then my loqThis(1, false, ("Found " & nameResultProject))
else
error (get my loqqed_Error_Halt5("A user collection named \"" & nameResultProject & "\" already exists, and it is not a project."))
end if
end if
end tell
set coll_ctr to 1
set nameResultAlbum to nameResultAlbumRoot & "_" & (get short date string of (get current date)) & "_"
repeat
tell application "Capture One 20" to tell ref2ResultProject
if not (exists collection named (get nameResultAlbum & coll_ctr)) then
set nameResultAlbum to (get nameResultAlbum & coll_ctr)
set ref2ResultAlbum to make new collection with properties {kind:album, name:nameResultAlbum}
exit repeat
else
set coll_ctr to coll_ctr + 1
end if
end tell
end repeat
if 0 < length of Coll_Init_Text then set Coll_Init_Text to Coll_Init_Text & ": "
my loqThis(0, false, (Coll_Init_Text & nameResultProject & ">" & nameResultAlbum))
return ref2ResultAlbum
end InitializeResultsCollection
on findParentColl(thisCollRef)
## General purpose handler to find the parent of a collection
## July 14 2020
global debugLogEnable
local errorText, errorText1, parentStringList, refPtr, docPtr, docName, parentPtr, parentRefList, startPtr, stopPtr
tell application "Capture One 20"
if (document = (class of thisCollRef)) then
if debugLogEnable then my loqThis(2, false, "Returned the document reference, no other parents")
return {thisCollRef}
end if
if (collection ≠ (class of thisCollRef)) then error my loqqed_Error_Halt5("findParentColl's parameter is not a collection or a document")
end tell
if debugLogEnable then my loqThis(2, false, "Starting Parent Search")
try
get || of {thisCollRef} -- intentionally create an error
on error errorText
if debugLogEnable then my loqThis(5, false, "Full error text \"" & errorText & "\"")
end try
## Extract the string between "{" and "}"
repeat with startPtr from 1 to count of errorText
if "{" = text startPtr of errorText then exit repeat
end repeat
repeat with stopPtr from -1 to -(count of errorText) by -1
if "}" = text stopPtr of errorText then exit repeat
end repeat
set errorText to my removeLeadingTrailingSpaces((text (startPtr + 1) thru (stopPtr - 1) of errorText))
## if script runs as an application "collection" is replaced by "«class COcl»", fix that
if "«class COcl»" = text 1 thru 12 of errorText then set errorText to my replaceText(errorText, "«class COcl»", "collection")
set parentStringList to my splitStringToList(errorText, "of") -- make a list of references
if debugLogEnable then my loqThis(4, false, "Processed error text \"" & errorText & "\"")
repeat with docPtr from (count of parentStringList) to 0 by -1
try
if "document" = first word of item docPtr of parentStringList then exit repeat
end try
end repeat
set {docPtr, parentRefList} to {(docPtr + 0), {}}
if 0 = docPtr then error my loqqed_Error_Halt5("findParentColl couldn't find \"document\" in the string \"" & errorText & "\"")
set docName to my removeLeadingTrailingSpaces((get item 2 of my splitStringToList((my removeLeadingTrailingSpaces((get item docPtr of parentStringList))), "\"")))
tell application "Capture One 20" to copy (document docName) to beginning of parentRefList
if debugLogEnable then my loqThis(5, false, ("Found Document Reference \"" & (get parentStringList's item docPtr) & "\""))
repeat with parentPtr from 1 to docPtr
if "collection" = (first word of item parentPtr of parentStringList) then exit repeat
end repeat
set parentPtr to parentPtr + 1
if (parentPtr > docPtr) then error my loqqed_Error_Halt5("findParentColl is unable to find the starting collection in the string \"" & errorText & "\"")
repeat with refPtr from docPtr - 1 to parentPtr by -1
if debugLogEnable then my loqThis(5, false, ("Collection Reference \"" & (get parentStringList's item refPtr) & "\""))
tell application "Capture One 20" to tell (first item of parentRefList) to copy (collection id (get last word of parentStringList's item refPtr)) to beginning of parentRefList
end repeat
return parentRefList
end findParentColl
on initLoqqingGlobals(debugLogLevel, Script_Title, posix2PrefFile, gateResultsFile, path2ResultsFiles, enableReadPrefs, enableWritePrefs)
## Handler to setup the Global Variables used by the loqqing system at the start of the script
## loqResultDocRef is kept separate from Loqqing to allow easy listing of Loqqing
-- , enableReadPrefs, enableWritePrefs
global debugLogEnable, Loqqing, parent_name
global loqResultDocRef, loqResultMethod, loqDialogTextList, loqClipboardTextS
## Setup for logging during initialisation
## Setup for logging without GUI and prefs file
## controlled by the enable settings only
## Logging by Clipboard, Notifications and Logging
## LoqqingVersion is not copied into Loqqing, not need without GUI
set {loqResultDocRef, loqResultMethod, loqDialogTextList, loqClipboardTextS} to {false, "Preliminary - Clipboard", (get Script_Title & " Startup"), (get Script_Title & " Startup")}
## the last word of the first line of loqDialogTextList and loqClipboardTextS must be "Startup"
set debugLogEnable to (get debugLogLevel > 0)
set Loqqing to {stateResultDoc:false, gateResultsFile:gateResultsFile, enableResultsFile:false, initResultDoc:false, nameResultDoc:(Script_Title & ".txt"), ResultsFileMaxDebug:0} & ¬
{stateResultsByClipboard:false, gateResultsByClipboard:false, enableResultsByClipboard:true, initResultsByClipboard:false, clipboardMaxDebug:6} & ¬
{stateResultsByDialog:false, gateResultsDialog:false, enableResultsByDialog:false, maxDialogPercent:50, maxDialogLines:25, maxDialogChar:1000, dialogTimeout:20} & ¬
{stateResultsByNotification:false, gateResultsNotification:false, enableResultsByNotifications:false, enableDebugNotifications:false, notificationsMaxDebug:0} & ¬
{stateResultsByLoq:false, gateParentLoqqing:true, enableResultsByLoq:true, initLoqqing:false, enableDebugByLoq:false} & ¬
{enableCompleteAlert:false, gateScriptProgressBar:false, enableScriptProgressBar:false} & ¬
{debugLogLevel:(0 + debugLogLevel), path2ResultsFiles:path2ResultsFiles, enableReadPrefs:enableReadPrefs, enableWritePrefs:enableWritePrefs} & ¬
{posix2PrefFile:posix2PrefFile, LoqVersion:LoqqingVersion, fullSetup:false, isChecked:false, enableFastGui:false, Script_Title:Script_Title} & ¬
{cleanExit:false, errorExit:false, errorExitCtr:0, startTick:my GetTick_Now(), stopTick:0}
if {"Script Editor", "Script Debugger"} contains parent_name then
set Loqqing's enableDebugByLoq to true
set loqResultMethod to loqResultMethod & ", Logs"
else
set Loqqing's enableDebugNotifications to true
set loqResultMethod to loqResultMethod & ", Notifications"
end if
return LoqqingVersion
end initLoqqingGlobals
on saveLoqqingPrefs() global Loqqing, debugLogEnable set posix2PrefFile to Loqqing's posix2PrefFile if ((get Loqqing's posix2PrefFile) = false) or (not Loqqing's fullSetup) then loqThis(3, false, "Logging Settings save was bypassed") loqThis(4, false, "Unable to save Logging Setup into Preference File: Path-" & (Loqqing's posix2PrefFile) & "; Full Setup-" & (Loqqing's fullSetup)) else tell application "System Events" to tell property list file (get Loqqing's posix2PrefFile) if (get name of every property list item) contains "Loqqing" then set value of property list item "Loqqing" to Loqqing else make new property list item at end with properties {kind:record, name:"Loqqing", value:Loqqing} end if end tell if debugLogEnable then loqThis(3, false, "Logging Settings saved into Settings File") end if if debugLogEnable then loqThis(4, false, "Settings File: Path-" & (Loqqing's posix2PrefFile) & "; Full Setup-" & (Loqqing's fullSetup)) end saveLoqqingPrefs
on loqqed_Error_Halt5(exitReason)
## General purpose handler for logging during script termination
global debugLogEnable, Loqqing
local plistNames, errorText, errorNumber, lastLine, countErrorExits
if false = (get Loqqing's posix2PrefFile) then
loqThis(3, false, ("loqqed_Error_Halt5() did not update settings file (expected, not configured)"))
else
try
tell application "System Events" to tell property list file (get Loqqing's posix2PrefFile)
if (get name of every property list item) contains "Loqqing" then
tell property list item "Loqqing"
set value of property list item "stopTick" to my GetTick_Now()
set countErrorExits to 1 + (value of property list item "errorExit")
set value of property list item "cleanExit" to false
set value of property list item "errorExit" to true
set value of property list item "errorExitCtr" to countErrorExits
end tell
my loqThis(3, false, ("loqqed_Error_Halt5 updated settings file for an error exit"))
else
my loqThis(2, false, ("loqqedNormalHalt6() error " & "property list item \"Loqqing\" was not found in .plist file"))
end if
end tell
on error errorText number errorNumber
my loqThis(0, false, ("loqqed_Error_Halt5() error \"" & errorText & "\""))
end try
end if
local clipText, exitText
set {clipText, exitText} to {"", ""}
if Loqqing's enableResultsByClipboard then set clipText to return & "Results are on the clipboard"
try
if 0 < (length of (get exitReason as text)) then set exitText to (" Exit Reason: " & exitReason & return)
end try
tell current application to set lastLine to "Script \"" & Loqqing's Script_Title & "\" exits with error at " & (get (current date) as text)
loqThis(-1, true, lastLine & exitText & clipText)
loqAnnounceResults(lastLine)
finalCleanup()
return lastLine & exitText
end loqqed_Error_Halt5
on loqqedNormalHalt6()
## General purpose handler for logging during script termination
global debugLogEnable, Loqqing
local plistNames, errorText, errorNumber, lastLine
if false = (get Loqqing's posix2PrefFile) then
loqThis(3, false, ("loqqed_Error_Halt6() did not update settings file (expected, not configured)"))
else
try
tell application "System Events" to tell property list file (get Loqqing's posix2PrefFile)
if (get name of every property list item) contains "Loqqing" then
tell property list item "Loqqing"
set value of property list item "stopTick" to my GetTick_Now()
set value of property list item "cleanExit" to true
set value of property list item "errorExit" to false
set value of property list item "errorExitCtr" to 0
end tell
my loqThis(3, false, ("loqqedNormalHalt6() updated settings file for a clean exit"))
else
my loqThis(2, false, ("loqqedNormalHalt6() error " & "property list item \"Loqqing\" was not found in .plist file"))
end if
end tell
on error errorText number errorNumber
my loqThis(0, true, ("loqqedNormalHalt6() error \"" & errorText & "\""))
end try
end if
tell current application to set lastLine to "Script \"" & Loqqing's Script_Title & "\" exits normally at " & (get (current date) as text)
local clipText
set clipText to ""
if Loqqing's enableResultsByClipboard then set clipText to return & "Results are on the clipboard"
loqThis(-1, true, lastLine & clipText)
loqAnnounceResults(lastLine)
finalCleanup()
return lastLine
end loqqedNormalHalt6
on loqqedNormalHalt7(exitMessage)
## General purpose handler for logging during script termination
global debugLogEnable, Loqqing
local plistNames, errorText, errorNumber, lastLine
if false = (get Loqqing's posix2PrefFile) then
loqThis(3, false, ("loqqed_Error_Halt7() did not update settings file (expected, not configured)"))
else
try
tell application "System Events" to tell property list file (get Loqqing's posix2PrefFile)
if (get name of every property list item) contains "Loqqing" then
tell property list item "Loqqing"
set value of property list item "stopTick" to my GetTick_Now()
set value of property list item "cleanExit" to true
set value of property list item "errorExit" to false
set value of property list item "errorExitCtr" to 0
end tell
my loqThis(3, false, ("loqqedNormalHalt7() updated settings file for a clean exit"))
else
my loqThis(2, false, ("loqqedNormalHalt7() error " & "property list item \"Loqqing\" was not found in .plist file"))
end if
end tell
on error errorText number errorNumber
my loqThis(0, false, ("loqqedNormalHalt7() error \"" & errorText & "\""))
end try
end if
tell current application to set lastLine to "\"" & Loqqing's Script_Title & "\" normal exit at " & (get (current date) as text) & exitMessage
local clipText
set clipText to ""
if Loqqing's enableResultsByClipboard then set clipText to return & "Results are on the clipboard"
loqThis(-1, true, lastLine & clipText)
loqAnnounceResults(lastLine)
finalCleanup()
return lastLine
end loqqedNormalHalt7
on loqAnnounceResults(lastLine) global debugLogEnable, loqClipboardTextS, parent_name, Loqqing local announceText tell application "System Events" to set frontmost of process parent_name to true set announceText to "\"" & Loqqing's Script_Title & "\" has completed" if Loqqing's enableResultsByClipboard then set the clipboard to loqClipboardTextS set announceText to announceText & return & "Results are on the clipboard" end if
## Avoiding Duplicate notifications and alerts
if not Loqqing's enableResultsByNotifications then display notification announceText
if Loqqing's enableCompleteAlert and not Loqqing's enableResultsByDialog then display alert announceText
end loqAnnounceResults
on loqGUIsettings2(theGuiData)
## General Purpose handler that creates the settings list for the Settings GUI for all the settings
script ResultsMgr_S
on resolve()
## Gets called after any change to ResultsByClipboard, ResultsByDialog, ResultsFile, enableResultsByLoq, ResultsByNotifications
global debugLogEnable, loqResultMethod, Loqqing
set loqResultMethod to setupLoqqing6()
end resolve
on preConfig()
global Result_DocName, loqResultMethod
set loqResultMethod to setupLoqqing6()
return return & "Result Reporting: " & loqResultMethod & return
end preConfig
on postConfig()
## Gets called before and after the GUI runs
global Result_DocName, loqResultMethod, Loqqing
if not (Loqqing's enableResultsByClipboard or Loqqing's enableResultsByDialog or Loqqing's enableResultsFile or Loqqing's enableResultsByLoq or Loqqing's enableResultsByNotifications) then
## search for some kind of enabled result reporting and turn it on
## in order of preference
if Loqqing's gateParentLoqqing then
set Loqqing's enableResultsByLoq to true
else if Loqqing's gateResultsFile then
set Loqqing's enableResultsFile to true
else
if Loqqing's gateResultsNotification then set Loqqing's enableDebugNotifications to true
if Loqqing's gateResultsDialog then
set Loqqing's enableResultsByDialog to true
else if Loqqing's gateResultsClipboard then
set Loqqing's enableResultsByClipboard to true
else if Loqqing's gateResultsNotification then
set Loqqing's enableResultsByNotifications to true
end if
end if
set loqResultMethod to setupLoqqing6()
display alert "All methods of reporting results have been disabled!! " message ("Enabling result reporting by " & loqResultMethod) as critical giving up after 30
else
set loqResultMethod to setupLoqqing6()
end if
return return & "Result Reporting: " & loqResultMethod & return
end postConfig
end script
script DebugMgr_S
on resolve()
global debugLogEnable, loqResultMethod, Loqqing
set loqResultMethod to setupLoqqing6()
end resolve
end script
local helpdebugLogLevel, helpMaxDebug, helpMaxDialog, helpFastGUI
set helpdebugLogLevel to "Maximum level of debug info reported"
set helpMaxDebug to "Maximum level of debug data in TextEdit file"
set helpMaxNotifications to "Maximum level of debug data in Notifications"
set helpMaxDialog to "Percentage of screen fill that triggers a Dialog report"
set helpFastGUI to "Disables Free Input for a faster workflow"
global debugLogEnable, Loqqing
local settingList, scriptList, theSetting_r
set settingList to {}
set end of settingList to {s_ID:0, s_Name:"Alert when Script ends", s_Value:(a reference to Loqqing's enableCompleteAlert), s_UserSet:true, s_Active:true, s_Class:"Boolean"}
set end of settingList to {s_ID:0, s_Name:"Report Results by Text File", s_Value:(a reference to Loqqing's enableResultsFile), s_UserSet:true, s_Active:Loqqing's gateResultsFile, s_Class:"Boolean", s_Script:ResultsMgr_S}
set end of settingList to {s_ID:0, s_Name:"Report Results by Logging", s_Value:(a reference to Loqqing's enableResultsByLoq), s_UserSet:true, s_Active:(a reference to Loqqing's gateParentLoqqing), s_Class:"Boolean", s_Script:ResultsMgr_S}
set end of settingList to {s_ID:0, s_Name:"Report Results by Clipboard", s_Value:(a reference to Loqqing's enableResultsByClipboard), s_UserSet:true, s_Active:Loqqing's gateResultsByClipboard, s_Class:"Boolean", s_Script:ResultsMgr_S}
set end of settingList to {s_ID:0, s_Name:"Report Results by Dialogs", s_Value:(a reference to Loqqing's enableResultsByDialog), s_UserSet:true, s_Active:Loqqing's gateResultsDialog, s_Class:"Boolean", s_Script:ResultsMgr_S}
set end of settingList to {s_ID:0, s_Name:"Dialog Timeout (s)", s_Help:helpMaxDialog, s_Value:(a reference to Loqqing's dialogTimeout), s_UserSet:true, s_Active:(a reference to Loqqing's enableResultsByDialog), s_Class:"Integer", s_LType:"ExMin&ExMax", s_Limit_L:{0, 120}}
set end of settingList to {s_ID:0, s_Name:"Length of Dialog Report (%)", s_Help:helpMaxDialog, s_Value:(a reference to Loqqing's maxDialogPercent), s_UserSet:true, s_Active:(a reference to Loqqing's enableResultsByDialog), s_Class:"Integer", s_LType:"ExMin&InMax", s_Limit_L:{0, 100}, s_Script:ResultsMgr_S}
set end of settingList to {s_ID:0, s_Name:"Report Results by Notifications", s_Value:(a reference to Loqqing's enableResultsByClipboard), s_UserSet:true, s_Active:Loqqing's gateResultsNotification, s_Class:"Boolean", s_Script:ResultsMgr_S}
set end of settingList to {s_ID:0, s_Name:"Enable Script Window's Progress Bar", s_Value:(a reference to Loqqing's enableScriptProgressBar), s_UserSet:true, s_Active:(a reference to Loqqing's gateScriptProgressBar), s_Class:"Boolean"}
set end of settingList to {s_ID:0, s_Name:"++++", s_Value:missing value, s_UserSet:missing value, s_Active:true}
set end of settingList to {s_ID:0, s_Name:"Debug Level", s_Help:helpdebugLogLevel, s_Value:(a reference to Loqqing's debugLogLevel), s_UserSet:true, s_Active:true, s_Class:"Integer", s_LType:"InMin&InMax", s_Limit_L:{0, 6}, s_Script:DebugMgr_S}
set end of settingList to {s_ID:0, s_Name:"Max Debug Level in Text File", s_Help:helpMaxDebug, s_Value:(a reference to Loqqing's ResultsFileMaxDebug), s_UserSet:true, s_Active:(a reference to Loqqing's enableResultsFile), s_Class:"Integer", s_LType:"InMin&InMax", s_Limit_L:{0, 6}}
set end of settingList to {s_ID:0, s_Name:"Max Debug Level in Clipboard", s_Help:helpMaxNotifications, s_Value:(a reference to Loqqing's clipboardMaxDebug), s_UserSet:true, s_Active:(a reference to Loqqing's enableResultsByClipboard), s_Class:"Integer", s_LType:"InMin&InMax", s_Limit_L:{0, 6}}
set end of settingList to {s_ID:0, s_Name:"Report Debug by Notifications", s_Value:(a reference to Loqqing's enableDebugNotifications), s_UserSet:Loqqing's gateResultsNotification, s_Active:true, s_Class:"Boolean", s_Script:ResultsMgr_S}
set end of settingList to {s_ID:0, s_Name:"Max Debug Level in Notifications", s_Help:helpMaxNotifications, s_Value:(a reference to Loqqing's notificationsMaxDebug), s_UserSet:true, s_Active:(a reference to Loqqing's enableDebugNotifications), s_Class:"Integer", s_LType:"InMin&InMax", s_Limit_L:{0, 2}}
set theGuiData's guiRecordList to theGuiData's guiRecordList & settingList
set theGuiData's guiScriptList to theGuiData's guiScriptList & {ResultsMgr_S}
set theGuiData's guiParams's guiChecked to a reference to Loqqing's isChecked
return null
end loqGUIsettings2
on setupLoqqing6()
## Handler to initialize logging of results
## Do use loq_Results() until the end of the handler and initialising is completed
## Doesn't set loqResultMethod, this is set by caller of this handler
global debugLogEnable
global parent_name, Loqqing, loqDialogTextList, loqResultDocRef, loqClipboardTextS
set debugLogEnable to (0 < (get Loqqing's debugLogLevel))
local LogMethods, LogHeader, date_string, originLine, initLoqCache, errorText, errorNumber
tell current application to set date_string to (current date) as text
set LogMethods to {}
set LogHeader to ("Script \"" & Loqqing's Script_Title & "\" results on " & date_string)
set originLine to (" by \"" & Loqqing's Script_Title & "\" on " & date_string)
if debugLogEnable then loqThis(3, false, "setupLoqqing6: Script Title \"" & Loqqing's Script_Title & "\"")
set initLoqCache to ""
if (0 < (count paragraphs of loqClipboardTextS)) and ¬
(0 < (count words of loqClipboardTextS's first paragraph)) and ¬
("Startup" = (get loqClipboardTextS's first paragraph's last word)) then -- capture startup logs
if (1 < (count paragraphs of loqClipboardTextS)) then ¬
set initLoqCache to "Cached Events:" & return & "---" & loqClipboardTextS & return & "---"
set loqClipboardTextS to ""
end if
local TextEditlist, DocName_Ext
if not Loqqing's gateResultsFile then
set {Loqqing's stateResultDoc, Loqqing's enableResultsFile, Loqqing's initResultDoc} to {false, false, false}
else
if debugLogEnable then loqThis(4, false, "Name Result Doc \"" & (Loqqing's nameResultDoc) & "\"; Enable Results File: " & (Loqqing's enableResultsFile) & "; Init Results File: " & (Loqqing's initResultDoc))
if Loqqing's enableResultsFile then
set DocName_Ext to Loqqing's nameResultDoc
set end of LogMethods to "TextEdit: " & DocName_Ext
if not Loqqing's initResultDoc then
## initResultDoc means that we have a valid loqResultDocRef and a header has been written
set Loqqing's stateResultDoc to false -- insurance
## If TextEdit is already open and has the document open then add the header
tell application "System Events" to set TextEditlist to get background only of every application process whose name is "TextEdit"
if (0 < (count of TextEditlist)) and not item 1 of TextEditlist then
if (DocName_Ext is in (get name of documents of application "TextEdit")) then
tell application "TextEdit" to tell document DocName_Ext
set loqResultDocRef to it
tell its text to set paragraph (1 + (count paragraphs)) to return & "-----" & return & LogHeader & return
end tell
set Loqqing's initResultDoc to true
if debugLogEnable then loqThis(3, false, "Path to \"" & DocName_Ext & "\" obtained" & originLine)
end if
end if
local targetFileWasCreated, targetFolderParent_a, targetFolderParent_p, targetFolderName, targetFolder_a, targetFolder_p, ResultDocPath_a, ResultDocPath_p, newFolderRef, newFileRef
if (not Loqqing's initResultDoc) then
## Do the full intialization since the document was not open
set targetFileWasCreated to false
set targetFolder_a to alias (get Loqqing's path2ResultsFiles)
set targetFolder_p to get POSIX path of targetFolder_a
set ResultDocPath_p to targetFolder_p & "/" & DocName_Ext
if debugLogEnable then loqThis(3, false, " Initializing \"" & DocName_Ext & "\" in \"" & targetFolder_p & "\"")
try
set ResultDocPath_a to (get alias POSIX file ResultDocPath_p)
if debugLogEnable then loqThis(4, false, "File " & ResultDocPath_p & " Exists")
on error errorText number errorNumber -- create the document
tell application "Finder" to set newFileRef to make new file at targetFolder_a with properties {name:DocName_Ext}
set ResultDocPath_a to newFileRef as alias
set targetFileWasCreated to true
end try
tell application "TextEdit" -- open the document and add the first line if empty
activate
set loqResultDocRef to open ResultDocPath_a
tell text of loqResultDocRef
if targetFileWasCreated then
set paragraph 1 to "Created" & originLine & return
my loqThis(3, false, ResultDocPath_p & " Created" & originLine)
else
if (0 = (count of paragraphs)) then
set paragraph 1 to "Initialised" & originLine & return
my loqThis(3, false, ResultDocPath_p & " Initialised" & originLine)
end if
end if
end tell
end tell
set Loqqing's initResultDoc to true -- prevents initialisation from repeating
tell application "TextEdit" to tell text of loqResultDocRef to ¬
set paragraph (1 + (count paragraphs)) to return & LogHeader & return
end if
## before the results document is initalized, logged events are stored in loqClipboardTextS, copy those to the results document
if ("" ≠ initLoqCache) then tell application "TextEdit" to tell text of loqResultDocRef to ¬
set paragraph (1 + (count paragraphs)) to initLoqCache
else
if debugLogEnable then loqThis(3, false, "\"" & DocName_Ext & "\" already initialized")
end if
if Loqqing's initResultDoc then
set Loqqing's stateResultDoc to true
else
loqThis(-1, true, "Unable to intialize results document")
set {Loqqing's stateResultDoc, Loqqing's enableResultsFile} to {false, false}
end if
end if
if Loqqing's stateResultDoc and not Loqqing's enableResultsFile then
set Loqqing's stateResultDoc to false
tell application "TextEdit" to tell text of loqResultDocRef to ¬
set paragraph (1 + (count paragraphs)) to return & parent_name & "Results reporting disabled for: " & Loqqing's Script_Title & return
end if
end if
local screenWidthO, screenHeightO, screenWidth, screenHeight, fontSize_pts, charactersPerLine, dotsPer_Point, linesPerScreen, borderWidth
if not Loqqing's gateResultsDialog then
set {Loqqing's stateResultsByDialog, Loqqing's enableResultsByDialog} to {false, false}
else
if Loqqing's enableResultsByDialog then
set end of LogMethods to "Dialogs"
if not Loqqing's stateResultsByDialog then
set loqDialogTextList to (get LogHeader & return)
set Loqqing's stateResultsByDialog to true
end if
## Debugged with the largest and smallest Mac screens - 11"MBA and 27" iMac
tell application "Finder" to set {screenWidthO, screenHeightO, screenWidth, screenHeight} to bounds of window of desktop
set fontSize_pts to 12 -- estimated font size for display dialog, including line spacing
set charactersPerLine to 58 -- estimated characters per line for display dialog, if there are no "return" characters
set dotsPer_Point to 1.5 -- similar for 5K 27" iMac and 11" MBA - similar for others - retina independent
set linesPerScreen to (screenHeight - screenHeightO) / (fontSize_pts * dotsPer_Point)
set borderWidth to 5 -- lines
set Loqqing's maxDialogLines to (get ((Loqqing's maxDialogPercent) / 100 * (linesPerScreen - borderWidth)) as integer)
set Loqqing's maxDialogChar to (get (charactersPerLine * (Loqqing's maxDialogLines)) as integer)
if debugLogEnable then loqThis(4, false, ("maxDialogPercent-" & (Loqqing's maxDialogPercent) & "; screenHeight-" & (screenHeight) & "; linesPerScreen-" & (linesPerScreen) & "; maxDialogLines-" & (Loqqing's maxDialogLines) & "; maxDialogChar-" & (Loqqing's maxDialogChar)))
else if Loqqing's stateResultsByDialog then
set Loqqing's stateResultsByDialog to false
--set loqDialogTextList to ""
end if
end if
if not Loqqing's gateResultsByClipboard then
set {Loqqing's stateResultsByClipboard, Loqqing's enableResultsByClipboard} to {false, false}
else
if Loqqing's enableResultsByClipboard then
if not Loqqing's initResultsByClipboard then
set loqClipboardTextS to (LogHeader & return) -- extra line
set Loqqing's initResultsByClipboard to true
if ("" ≠ initLoqCache) then set loqClipboardTextS to return & loqClipboardTextS & initLoqCache & return -- extra line
end if
if not Loqqing's stateResultsByClipboard then
set Loqqing's stateResultsByClipboard to true
set loqClipboardTextS to loqClipboardTextS & return & "Clipboard Results enabled"
end if
else if Loqqing's stateResultsByClipboard then
set Loqqing's stateResultsByClipboard to false
set loqClipboardTextS to loqClipboardTextS & return & "Clipboard Results disabled"
end if
end if
set Loqqing's gateScriptProgressBar to (get ("Script Editor" = parent_name)) -- gateScriptProgressBar controls user's ability to set progress bar in the Script Editor window
if not Loqqing's gateScriptProgressBar then set Loqqing's enableScriptProgressBar to false
if {"Script Editor", "Script Debugger"} contains parent_name then set Loqqing's enableDebugByLoq to true
## enableDebugByLoq sets the logging of debug information
## Don't capture initLoqCache - if enableDebugByLoq would be true then it is already captured.
## Logging of results -- gateParentLoqqing controls user's ability to set logging of results
if Loqqing's gateResultsNotification and Loqqing's enableResultsByLoq and not Loqqing's initLoqqing then
set Loqqing's stateResultsByLoq to false
if ("Script Editor" = parent_name) then
try -- Open the Log History window
tell application "System Events" to tell application process "Script Editor"
if (get name of windows) does not contain "log History" then ¬
click menu item "Log History" of menu "Window" of menu bar 1
end tell
end try
set Loqqing's initLoqqing to true
else if ("Script Debugger" = parent_name) then
try
## Script avoids compiler errors when Script Debugger is not installed
run script "tell application \"Script Debugger\" to tell first document to set event log visible to true"
run script "tell application \"Script Debugger\" to tell first document to set event log scope bar visible to true"
end try
set Loqqing's initLoqqing to true
else
tell Loqqing to set {its gateParentLoqqing, enableResultsByLoq} to {false, false}
end if
end if
if not Loqqing's gateParentLoqqing then
tell Loqqing to set {its stateResultsByLoq, its enableResultsByLoq} to {false, false}
else if Loqqing's enableResultsByLoq then
set end of LogMethods to " " & parent_name & " Log"
if not Loqqing's stateResultsByLoq then
set Loqqing's stateResultsByLoq to true
if debugLogEnable then loqThis(3, false, "Results Logging enabled for: " & Loqqing's Script_Title)
end if
else if Loqqing's stateResultsByLoq then
set Loqqing's stateResultsByLoq to false
if debugLogEnable then loqThis(3, false, "Results Logging disabled for: " & Loqqing's Script_Title)
end if
if not Loqqing's gateResultsNotification then
set {Loqqing's stateResultsByNotification, Loqqing's enableResultsByNotifications} to {false, false}
else
if Loqqing's enableResultsByNotifications then
set end of LogMethods to "Notifications"
set Loqqing's stateResultsByNotification to true
else if Loqqing's stateResultsByNotification then
display notification "Notifications disabled for: " & Loqqing's Script_Title
set Loqqing's stateResultsByNotification to false
end if
end if
set LogMethods_S to my joinListToString(LogMethods, ", ")
if debugLogEnable then loqThis(3, false, ("Result Reported by " & LogMethods_S))
return LogMethods_S
end setupLoqqing6
on loqThis(thisLogDebugLevel, MakeFront, log_Text)
## General purpose handler for logging results
## log results if the debug level of the message is below the the threshold set by debugLogLevel
## log the results by whatever mechanism is enabled - {Script Editor Log, Text Editor Log, Display Dialog}
global debugLogEnable
global parent_name, Loqqing, loqDialogTextList, loqResultDocRef, loqClipboardTextS
local log_Text_S, dialogTriggeredMakeFront
if thisLogDebugLevel > Loqqing's debugLogLevel then return "" -- immediate return if thisLogDebugLevel exceeds threshold
set log_Text_S to my joinListToString(log_Text, "; ")
set dialogTriggeredMakeFront to false
## Dialog
if Loqqing's stateResultsByDialog and (thisLogDebugLevel ≤ 0) then
set loqDialogTextList to (get loqDialogTextList & return & log_Text_S)
if MakeFront or (0 ≥ Loqqing's maxDialogLines) or ¬
(Loqqing's maxDialogLines < (get count of paragraphs of loqDialogTextList)) or ¬
(Loqqing's maxDialogChar < (get length of loqDialogTextList)) then
tell application "System Events" to set frontmost of process parent_name to true
set dialogTriggeredMakeFront to true
set dialogResult to display dialog loqDialogTextList with title "Report for " & Loqqing's Script_Title & " (timeout " & (Loqqing's dialogTimeout) & ¬
"s)" buttons {"Continue", "Hold"} default button "Continue" giving up after Loqqing's dialogTimeout -- assignment prevents Apple Event timeout
if gave up of dialogResult then set dialogResult to ¬
display dialog loqDialogTextList with title "Hold this Dialog?" buttons {"Continue", "Hold"} default button "Continue" giving up after 10
if button returned of dialogResult is "Hold" then set dialogResult to ¬
display dialog loqDialogTextList with title "Report for " & Loqqing's Script_Title & " (holding) " buttons {"Continue"} default button "Continue"
set loqDialogTextS to ""
end if
end if
## Result Document
if Loqqing's stateResultDoc and (thisLogDebugLevel ≤ Loqqing's ResultsFileMaxDebug) then ¬
tell application "TextEdit" to tell text of loqResultDocRef to ¬
set paragraph (1 + (count paragraphs)) to ((log_Text_S as text) & return)
## Log
if Loqqing's enableDebugByLoq and (thisLogDebugLevel ≥ 0) then log (log_Text)
if Loqqing's enableResultsByLoq and (thisLogDebugLevel < 0) then log (log_Text)
## Clipboard
if Loqqing's enableResultsByClipboard and (thisLogDebugLevel ≤ Loqqing's clipboardMaxDebug) then ¬
set loqClipboardTextS to (loqClipboardTextS & return & log_Text_S)
## Notifications
## If a notification has too many characters then the notification system hangs
## Constrain it to 3 lines of 39 characters max.
local paraCtr, notString, lineCnt, paramax, remChar, thePara, thisCount
if (Loqqing's enableDebugNotifications and (thisLogDebugLevel ≤ Loqqing's notificationsMaxDebug)) or ¬
(Loqqing's stateResultsByNotification and (thisLogDebugLevel ≤ 0)) then
set paraCtr to 0
set notString to ""
set lineCnt to 39
set paramax to 3
copy (get paramax * lineCnt) to remChar
repeat with thePara in (get paragraphs of (get my joinListToString(log_Text, return)))
set thisCount to (get count of (get contents of thePara))
if thisCount > 0 then
set paraCtr to paraCtr + 1
if thisCount > remChar then copy remChar to thisCount
set notString to notString & (get text 1 thru thisCount of thePara)
set remChar to remChar - lineCnt * (thisCount div lineCnt)
if (0 < (thisCount mod lineCnt)) then set remChar to remChar - lineCnt
if (paramax ≤ paraCtr) or (0 ≥ remChar) then exit repeat
set notString to notString & return
end if
end repeat
display notification notString
end if
if (not dialogTriggeredMakeFront) and (MakeFront or (0 = thisLogDebugLevel)) then --
if Loqqing's enableDebugByLoq then tell application "System Events" to set frontmost of process parent_name to true
if Loqqing's stateResultDoc then tell application "System Events" to set frontmost of process "TextEdit" to true
end if
return log_Text_S
end loqThis
on initGuiGlobals() global enableFastGui, checkedGuiData set checkedGuiData to false set enableFastGui to false return {guiRecordList:{}, guiScriptList:{}, guiParams:{guiChecked:false}} end initGuiGlobals
on initPrefsFromFile(Script_Title, debugLogLevel, errorExitMax, gateReadPrefFiles, gateWritePrefFiles, path2Prefs, gateResultsLogFiles, path2ResultsLogFiles, mainsLoqqingVersion, forceSettingsCheck) global debugLogEnable, Loqqing, loqResultDocRef, checkedGuiData local path2Prefs, alias2Prefs, posix2Prefs, path2PrefFile, posix2PrefFile, hasPrefFile, verifiedLoqPlist, gotPrefSetUp, this_plistfile, LoqqingVersion, errorText, errorNumber local prefsLoqVersion, prefsExitCtr, prefsErrorExit, prefsCleanExit, prefsFullSetup, hasLoqPlist set {posix2PrefFile, hasPrefFile, hasLoqPlist, verifiedLoqPlist, gotPrefSetUp} to {false, false, false, false, false}
## very simple logging setup, text edit logging not ready yet, preferences file not read yet
set checkedGuiData to false
set LoqqingVersion to my LoqqingVersion
if mainsLoqqingVersion ≠ LoqqingVersion then error my loqqed_Error_Halt5("SW Error: Loqqing Version Mismatch in GUI Library")
if (gateReadPrefFiles or gateWritePrefFiles) then
set path2PrefFile to path2Prefs & Script_Title & ".plist"
set posix2PrefFile to (POSIX path of path2PrefFile)
-- Do not use finder to test for the file existence because it has a bug that ignores leading 0's
-- https://www.macscripter.net/viewtopic.php?id=45178
--hasPrefFile, hasLoqPlist, verifiedLoqPlist, gotPrefSetUp
try
get path2PrefFile as alias -- check for existance of preferences .plist file
set hasPrefFile to true
## now check if its readable and the Preferences are any good
tell application "System Events"
set prefsLoqVersion to value of (property list file posix2PrefFile)'s property list item "Loqqing"'s property list item "LoqVersion"
set hasLoqPlist to true -- minimum condition for readable plist is met
set prefsExitCtr to value of (property list file posix2PrefFile)'s property list item "Loqqing"'s property list item "errorExitCtr"
set prefsErrorExit to value of (property list file posix2PrefFile)'s property list item "Loqqing"'s property list item "errorExit"
set prefsCleanExit to value of (property list file posix2PrefFile)'s property list item "Loqqing"'s property list item "cleanExit"
set prefsFullSetup to value of (property list file posix2PrefFile)'s property list item "Loqqing"'s property list item "fullSetup"
end tell
if (not prefsErrorExit) and (not prefsCleanExit) then set prefsExitCtr to prefsExitCtr + 1
set rejectList to {}
if not (LoqqingVersion = prefsLoqVersion) then set rejectList to rejectList & "Incorrect Version: Is " & prefsLoqVersion & " Expected " & LoqqingVersion
if (prefsExitCtr ≥ errorExitMax) then set rejectList to rejectList & "Error Exit Count too high: Is " & prefsExitCtr & " Limit " & errorExitMax
if not prefsFullSetup then set rejectList to rejectList & "Contains a preliminary setup"
if (0 = (get count of rejectList)) then
set verifiedLoqPlist to true
my loqThis(3, false, "Preferences file has passed verification")
else
set rejectList to (my joinListToString(rejectList, ", ")) & " (File \"" & posix2PrefFile & "\")"
my loqThis(2, false, "Rejected Preferences file: " & rejectList)
end if
on error errorText number errorNumber
my loqThis(-1, false, ("Got an error reading the preferences file: " & return & errorText & " (" & errorNumber & ")"))
end try
if gateReadPrefFiles and verifiedLoqPlist then
## now get the entire Loqqing record
--hasPrefFile, hasLoqPlist, verifiedLoqPlist, gotPrefSetUp
try
tell application "System Events" to tell property list file posix2PrefFile to copy (get value of property list item "Loqqing") to Loqqing
set gotPrefSetUp to true
set checkedGuiData to true
on error errorText number errorNumber
## something wrong with the file's plist. State of Loqqing is unknown
set verifiedLoqPlist to false
my loqThis(-1, true, ("Could not read the .plist file due to error: " & return & errorText & " (" & errorNumber & ")"))
end try
if gotPrefSetUp then
tell Loqqing
set {its startTick, its stopTick} to {my GetTick_Now(), 0}
set its posix2PrefFile to posix2PrefFile
set {its initResultDoc, its stateResultDoc, its initResultsByClipboard, its stateResultsByClipboard, its stateResultsByDialog, its initLoqqing, its stateResultsByLoq, its stateResultsByNotification} to ¬
{false, false, false, false, false, false, false, false}
if forceSettingsCheck then set its isChecked to false -- can be true or false when read
set {its enableReadPrefs, its enableWritePrefs, its Script_Title} to {gateReadPrefFiles, gateWritePrefFiles, Script_Title}
copy (0 + (its debugLogLevel)) to debugLogLevel
end tell
set loqResultMethod to my setupLoqqing6()
my loqThis(3, false, "Starting with " & loqResultMethod & "; Loqqing Preferences have been loaded from the .plist file")
else
my initLoqqingGlobals(debugLogLevel, Script_Title, posix2PrefFile, gateResultsLogFiles, path2ResultsLogFiles, gateReadPrefFiles, gateWritePrefFiles)
if debugLogEnable then my loqThis(3, false, ("Loqqing Full Setup, preferences file ignored"))
set {gotPrefSetUp, verifiedLoqPlist} to {false, false}
end if
end if
if gateWritePrefFiles then
if gotPrefSetUp then
my saveLoqqingPrefs() -- update preference file with modified settings
else
## The preferences file wasn't used
local parent_dictionary, theFile
if (not hasPrefFile) or (not hasLoqPlist) or (not verifiedLoqPlist) then -- there is no prefs file, or the prefs file plist cannnot be read, or the prefs file plist cannnot be verified
tell application "System Events" -- make a new prefs file
set the parent_dictionary to make new property list item with properties {kind:record} -- create an empty property list dictionary item
set theFile to make new property list file with properties {contents:parent_dictionary, name:posix2PrefFile} -- create new property list file using the empty dictionary list item as contents
tell theFile to make new property list item at end with properties {kind:record, name:"Loqqing", value:{LoqVersion:"0"}}
end tell
set Loqqing's posix2PrefFile to posix2PrefFile
set {hasPrefFile, hasLoqPlist, checkedGuiData} to {true, true, false}
else
set Loqqing's posix2PrefFile to posix2PrefFile
## The preferences file will be updated after full setup is completed
end if
end if
end if
end if
return
end initPrefsFromFile
on guisGUiSettings(theGuiData) global debugLogEnable, Loqqing
script PrefMgr_S
on preConfig()
global debugLogEnable, Loqqing
local prefMethod, prefFileName
if (false = (get Loqqing's posix2PrefFile)) then
set prefMethod to "Settings File not used"
else
set prefFileName to last item of my splitStringToList(get Loqqing's posix2PrefFile, "/")
set prefMethod to "Settings File: " & prefFileName & return
set prefMethod to prefMethod & "Read Settings: " & (Loqqing's enableReadPrefs) & " Write Settings: " & (Loqqing's enableWritePrefs) & return
set prefMethod to return & prefMethod
end if
return prefMethod
end preConfig
on postConfig()
global debugLogEnable
preConfig()
end postConfig
end script
local helpFastGUI, settingList
set helpFastGUI to "Disables Free Input for a faster workflow"
set settingList to {}
set end of settingList to {s_ID:0, s_Name:"Fast WorkFlow", s_Help:helpFastGUI, s_Value:(a reference to Loqqing's enableFastGui), s_UserSet:true, s_Active:true, s_Class:"Boolean"}
set theGuiData's guiParams to {enableFastGui:(a reference to Loqqing's enableFastGui), Script_Title:(get "" & Loqqing's Script_Title)} & theGuiData's guiParams
set theGuiData's guiRecordList to settingList & theGuiData's guiRecordList
set theGuiData's guiScriptList to theGuiData's guiScriptList & {PrefMgr_S}
return null
end guisGUiSettings
on guiRunSettingsEditor(theGuiData)
## General Purpose handler that provides a user interface to control settings for Script
## This is the main handler that runs the GUI
global debugLogEnable, parent_name
local nextID, idCtr, scriptList
local scriptDisplay_S, configDisplay_S, scriptCount, scriptCtr, theScript, legendString
local usercancelled, errorText, errorNumber, dialog_result
set usercancelled to false
set scriptList to theGuiData's guiScriptList
set idCtr to 1
repeat with nextID from 1 to (count of theGuiData's guiRecordList)
if not (missing value = theGuiData's guiRecordList's record nextID's s_UserSet) then
set theGuiData's guiRecordList's record nextID's s_ID to idCtr
if debugLogEnable then my loqThis(4, false, ("Name: \"" & (theGuiData's guiRecordList's record nextID's s_Name) & "\" ID:" & (theGuiData's guiRecordList's record nextID's s_ID)))
set idCtr to 1 + idCtr
end if
end repeat
guiValidateSettingsRecords(theGuiData)
set {configDisplay_S, scriptCount} to {"", (count of scriptList)}
if scriptCount > 0 then
repeat with scriptCtr from 1 to scriptCount
set theScript to scriptList's script scriptCtr
try
set scriptDisplay_S to theScript's preConfig()
if text = class of scriptDisplay_S then set configDisplay_S to configDisplay_S & scriptDisplay_S
on error errorText number errorNumber
if -1708 = errorNumber then
my loqThis(4, false, "Did not find script " & (get theScript's name) & "'s preConfig() handler")
else
my loqThis(2, false, "Got an error from script " & (get theScript's name) & ": " & errorText & " (" & errorNumber & ")")
end if
end try
end repeat
end if
repeat while not usercancelled
tell application "System Events" to set frontmost of process parent_name to true
set settingsDisplay_S to guiMakeSettingsDisplay(theGuiData, true) & configDisplay_S
set legendString to return & " • \"Edit\" to edit settings" & return & " • \"OK\" to continue with these settings" & return & " • \"Cancel\" to stop now"
if theGuiData's guiParams's enableFastGui then set legendString to (return & "Disable \"Fast WorkFlow\" to enable more control" & return) & legendString
set dialog_result to display dialog settingsDisplay_S & legendString with title "Settings for " & theGuiData's guiParams's Script_Title ¬
buttons {"Cancel Script", "OK", "Edit"} default button "OK"
if (get dialog_result's button returned) contains "Cancel" then
my loqThis(3, true, "guiRunSettingsEditor: User Cancelled the Script")
set usercancelled to true
exit repeat
end if
if "OK" = (get button returned of dialog_result) then exit repeat
if "Edit" = (get button returned of dialog_result) then
set usercancelled to guiSelectSetting(theGuiData)
if theGuiData's guiParams's enableFastGui then exit repeat
end if
end repeat
set configDisplay_S to ""
if scriptCount > 0 then
repeat with scriptCtr from 1 to scriptCount
set theScript to scriptList's script scriptCtr
try
set scriptDisplay_S to theScript's postConfig()
if text = class of scriptDisplay_S then set configDisplay_S to configDisplay_S & scriptDisplay_S
on error errorText number errorNumber
if -1708 = errorNumber then
my loqThis(4, false, "Did not find script " & (get theScript's name) & "'s postConfig() handler")
else
my loqThis(2, false, "Got an error from script " & (get theScript's name) & ": " & errorText & " (" & errorNumber & ")")
end if
end try
end repeat
end if
if debugLogEnable then
set settingsDisplay_S to guiMakeSettingsDisplay(theGuiData, false) & configDisplay_S
my loqThis(1, false, (return & "Post GUI Settings:" & return & settingsDisplay_S))
end if
return usercancelled
#############
## Definition of the Setting record
## s_ID ID of the setting (integer)
## s_Name Name of the Setting (text)
## s_Help Help Text for the Setting (text)
## s_Value Value or Reference to the Global Variable which holds the option value (text or reference to {text, integer, real, list})
## s_UserSet True if the user can edit this setting (boolean)
## false if the user cannot edit this setting (boolean)
## missing value if this is a static setting - never edited
## s_Active True if this setting is used; if false never shown (boolean)
## S_Invert Inverts the behaviour of s_Active if True, Ignored otherwise
## s_Class Class of the data representing the option {Boolean, Text, List_Text, Integer, Real} (text)
## s_LType How the option values are constrained {false, "Free", "List", "InMin", "InMax", "ExMin", "ExMax","NoSpace","OkSpace"}
## multiple values in one string are permitted, seperated by "&"
## s_Limit_L The list of permissible values of the option (List of Text, List of Integer, List of Real, List of [Min],[Max])
## s_Block_L A list of blocked characters
## s_Script A script containing a resolve() handler which performs actions after a setting has changed.
#############
end guiRunSettingsEditor
on guiMakeSettingsDisplay(theGuiData, showSettable)
## General Purpose handler that creates a string for displaying the script settings in the settings GUI.
global debugLogEnable
local settingsDisplay_S, theSettingRecord, theSettingValueS, xor, settingsList
set settingsList to theGuiData's guiRecordList
set settingsDisplay_S to ""
repeat with recordCtr from 1 to (count of settingsList)
copy (get settingsList's record recordCtr) to theSettingRecord
set xor to true
try
if (true = (contents of theSettingRecord's s_Invert)) then set xor to false
end try
if (xor = (contents of theSettingRecord's s_Active)) then
if showSettable then
if true = (get theSettingRecord's s_UserSet) then
set settingsDisplay_S to settingsDisplay_S & "+ "
else
set settingsDisplay_S to settingsDisplay_S & " "
end if
end if
set settingsDisplay_S to settingsDisplay_S & (get theSettingRecord's s_Name)
if (missing value ≠ (get theSettingRecord's s_Value)) then
if (missing value = (get theSettingRecord's s_UserSet)) then
set theSettingValueS to "" & (get contents of theSettingRecord's s_Value)
else
set theSettingClassS to "" & (get contents of theSettingRecord's s_Class)
if (theSettingClassS contains "list") then
if (theSettingClassS contains "text") then
if 0 < (count of (get contents of theSettingRecord's s_Value)) then
## set theSettingValueS to "{\"" & (my joinListToString((contents of theSettingRecord's s_Value), "\",\"")) & "\"}"
copy ("{\"" & (my joinListToString((get contents of theSettingRecord's s_Value), "\",\"")) & "\"}") to theSettingValueS
else
set theSettingValueS to "{}"
end if
else
## set theSettingValueS to "{" & (my joinListToString((contents of theSettingRecord's s_Value), ",")) & "}"
copy ("{" & (my joinListToString((get contents of theSettingRecord's s_Value), ",")) & "}") to theSettingValueS
end if
else if (theSettingClassS = "text") then
## set theSettingValueS to "\"" & (contents of theSettingRecord's s_Value) & "\""
copy ("\"" & (get contents of theSettingRecord's s_Value) & "\"") to theSettingValueS
else
## set theSettingValueS to "" & (contents of theSettingRecord's s_Value)
copy ("" & (get contents of theSettingRecord's s_Value)) to theSettingValueS
end if
end if
set settingsDisplay_S to settingsDisplay_S & ": " & theSettingValueS
end if
set settingsDisplay_S to settingsDisplay_S & return
end if
end repeat
return settingsDisplay_S
end guiMakeSettingsDisplay
on guiValidateSettingsRecords(theGuiData)
## General purpose handler that checks a setting record for inconsistancies
global debugLogEnable
local errText, errNum
if (true and (get contents of (theGuiData's guiParams's guiChecked))) then
if debugLogEnable then my loqThis(3, false, "Validation of GUI data bypassed")
else
if debugLogEnable then my loqThis(3, false, "Validation of GUI data has started")
local theRecord, theSettingName, validationFail, theSettingRecord, complaintList, recordCtr, xor
set validationFail to false
repeat with recordCtr from 1 to (count of theGuiData's guiRecordList)
set theSettingRecord to theGuiData's guiRecordList's record recordCtr
set complaintList to {}
set {isReferenced, xor} to {(my isARef(theSettingRecord's s_Active)), true}
try
set isReferenced to isReferenced or (my isARef(theSettingRecord's s_Invert))
if (true = (contents of theSettingRecord's s_Invert)) then set xor to false
end try
if (isReferenced or (xor = (contents of theSettingRecord's s_Active))) and (missing value ≠ (get theSettingRecord's s_UserSet)) then
set theRecord to guiParseSettingRecord(theSettingRecord)
if theRecord's ParseFail then
set the end of complaintList to (get theRecord's ParseErrorString)
else
local ParseError, ParseErrorList, theSettingName, theSettingValue, theValueCLassName, theSettingLimitList, hasSettingLimitList, hasFreeInput, theSettingBlockList
local hasSettingBlockList, blockLTSpaces, theSettingMin, hasInclusiveMin, theSettingMax, hasInclusiveMax, hasExclusiveMin, hasExclusiveMax, hasQuotation, hasNoSpace, hasOkSpace
tell theRecord
set {ParseError, ParseErrorList, theSettingName, theSettingValue, theValueCLassName, valueIsList, theSettingLimitList, hasSettingLimitList, hasFreeInput, theSettingBlockList, hasSettingBlockList, blockLTSpaces, theSettingMin, hasInclusiveMin, theSettingMax, hasInclusiveMax, hasExclusiveMin, hasExclusiveMax, hasQuotation, hasNoSpace, hasOkSpace} to ¬
{(its ParseError), (its ParseErrorList), (its settingName), (its settingValue), (its valueCLassName), (its valueIsList), (its settingLimitList), (its hasSettingLimitList), (its hasFreeInput), (its SettingBlockList), (its hasSettingBlockList), (its blockLTSpaces), (its settingMin), (its hasInclusiveMin), (its settingMax), (its hasInclusiveMax), (its hasExclusiveMin), (its hasExclusiveMax), (its hasQuotation), (its hasNoSpace), (its hasOkSpace)}
end tell
if ParseError then set complaintList to complaintList & ParseErrorList
if hasSettingLimitList and (not hasFreeInput) and (0 = (count of theSettingLimitList)) then set the end of complaintList to ("Setting \"" & theSettingName & "\" has an empty Choose List and not Free Input")
if {"real", "integer"} contains theValueCLassName then
if (hasInclusiveMin and (theSettingValue < theSettingMin)) or (hasExclusiveMin and (theSettingValue ≤ theSettingMin)) then set the end of complaintList to ("The value of \"" & theSettingName & "\" is less than it's minimum")
if (hasInclusiveMax and (theSettingValue > theSettingMax)) or (hasExclusiveMax and (theSettingValue ≥ theSettingMax)) then set the end of complaintList to ("The value of \"" & theSettingName & "\" is greater than it's maximum")
else -- limits valid only for real and integer should not be used elsewhere
if hasInclusiveMin or hasExclusiveMin or hasInclusiveMax or hasExclusiveMax then set the end of complaintList to ("Setting \" " & settingName & "\" has a \"Min\" or \"Max\" limit which is invalid for type \"" & valueCLassName & "\"")
end if
local initialValue, theSettingLimitList
if ("text" = theValueCLassName) then
if hasNoSpace and hasOkSpace then set the end of complaintList to ("Setting \"" & settingName & "\" has an \"OkSpace\" limit and a \"NoSpace\" limit - invalid combination")
if hasOkSpace and (hasSettingBlockList and ((theSettingBlockList contains " ") or (theSettingBlockList contains "\""))) then ¬
set the end of complaintList to ("Setting \"" & settingName & "\" has an \"OkSpace\" limit and a Setting Block List which contains Space or the Quotation symbol - invalid combination")
if hasSettingLimitList then
copy theSettingLimitList to initialValue
if hasSettingBlockList then set theSettingLimitList to my removeTextFromList(theSettingLimitList, theSettingBlockList)
if blockLTSpaces then set theSettingLimitList to my removeLeadingTrailingSpaces(theSettingLimitList)
if not (initialValue = theSettingLimitList) then set the end of complaintList to ("Setting \"" & theSettingName & "\" has blocked or prohibited characters in it's Choose List")
end if
if 0 < (length of theSettingValue) then
copy (theSettingValue as list) to initialValue
if hasSettingBlockList then set theSettingValue to my removeTextFromList(theSettingValue, theSettingBlockList) -- result is a list
if blockLTSpaces then set theSettingValue to my removeLeadingTrailingSpaces(theSettingValue)
if not (initialValue = (get theSettingValue as list)) then set the end of complaintList to ("Setting \"" & theSettingName & "\" has blocked or prohibited characters in it's setting")
end if
else -- limits valid only for text should not be used elsewhere
if hasSettingBlockList then set the end of complaintList to ("Setting \" " & settingName & "\" has a \"Blocked Characters\" limit which is invalid for type \"" & valueCLassName & "\"")
if hasNoSpace or hasOkSpace then set the end of complaintList to ("Setting \" " & settingName & "\" has a limit type which is invalid for type \"" & valueCLassName & "\"")
end if
local badList
if (not hasFreeInput) and hasSettingLimitList then
if valueIsList then
set badList to {}
repeat with theValue in theSettingValue
if (theSettingLimitList does not contain (get contents of theValue)) then set end of badList to (get contents of theValue)
end repeat
if 0 < (count of badList) then set the end of complaintList to {0, false, ("Values \"" & my joinListToString(badList, ("\", \"")) & "\" of setting \"" & theSettingName & "\" are not in it's Choose List and Free Input is not enabled")}
else
if (theSettingLimitList does not contain theSettingValue) then set the end of complaintList to ("The value of setting \"" & theSettingName & "\" is not in it's Choose List and Free Input is not enabled")
end if
end if
end if
local theComplaint
if (0 < (count of complaintList)) then
set validationFail to true
repeat with theComplaint in the complaintList
my loqThis(-1, false, theComplaint)
end repeat
end if
end if
end repeat
if validationFail then error my loqqed_Error_Halt5("Validation of GUI data has failed ")
try
set contents of (theGuiData's guiParams's guiChecked) to true
on error errText number errNum
my loqThis(-1, true, ("error " & errNum & " :" & errText))
end try
my loqThis(3, false, "Validation of GUI data successfully completed")
end if
end guiValidateSettingsRecords
on guiParseSettingRecord(theSettingRecord)
## General Purpose handler that parses one setting record
global debugLogEnable
ignoring case -- for this entire handler
local settingName, settingCLassName, valueIsList, valueCLassName, settingValue, ParseError, ParseErrorList, xor
set ParseError to false -- minor errors
set ParseErrorList to {} -- minor errors
set settingName to (get contents of theSettingRecord's s_Name)
if (missing value = (get contents of theSettingRecord's s_UserSet)) then
return {ParseFail:true, ParseErrorString:("SW Error: Setting \" " & settingName & "'s\" s_UserSet is missing value")} -- may cause unexpected behaviour
end if
-- set settingCLassName to (get theSettingRecord's s_Class) -- works
set settingCLassName to (get contents of theSettingRecord's s_Class)
if (get settingCLassName contains "list") then
set valueIsList to true
if (get settingCLassName contains "text") then
set valueCLassName to "text"
else -- error that will cause a failure to edit
return {ParseFail:true, ParseErrorString:("Setting \" " & settingName & "\" has an unexpected value in Setting Class: \"" & settingCLassName & "\"")}
end if
else
set valueCLassName to (get settingCLassName as text)
set valueIsList to false
end if
if {"text", "integer", "real", "boolean"} does not contain valueCLassName then -- error that will cause a failure to edit
return {ParseFail:true, ParseErrorString:("SW Error: Setting \" " & settingName & "\" has an unexpected value in Setting Class: \"" & valueCLassName & "\"")}
end if
(get contents of theSettingRecord's s_Value)
set settingValue to my deReference((get contents of theSettingRecord's s_Value), valueCLassName)
local SettingBlockList, hasSettingBlockList
set SettingBlockList to missing value
if "text" = valueCLassName then
if valueIsList then
set settingValue to settingValue as list
else
set settingValue to settingValue as text
end if
end if
try
set SettingBlockList to (get contents of theSettingRecord's s_Block_L)
set hasSettingBlockList to true
on error
set hasSettingBlockList to false
end try
local hasFreeInput, hasSettingLimitType, hasInclusiveMin, hasInclusiveMax, hasSettingLimitList, hasSettingLimitList, SettingLimitType, settingMin, settingMax, settingLimitList
local hasQuotation, hasNoSpace, hasOkSpace
set {hasFreeInput, hasSettingLimitType, hasInclusiveMin, hasInclusiveMax, hasExclusiveMin, hasExclusiveMax, hasSettingLimitList, hasQuotation, hasNoSpace, hasOkSpace} to ¬
{false, false, false, false, false, false, false, false, false, false}
set {settingMin, settingMax, settingLimitList, SettingLimitType} to {missing value, missing value, missing value, missing value}
local SettingLimitType
if (valueCLassName = "Boolean") then
set SettingLimitType to "boolean"
set hasSettingLimitType to true
else
try
set SettingLimitType to (get contents of theSettingRecord's s_LType) as text -- this works even if the class is boolean
set hasSettingLimitType to true
if "false" = SettingLimitType then set hasSettingLimitType to false
on error
set hasSettingLimitType to false
end try
end if
local theLimitType, SettingLimitType_L, sltCtr
if hasSettingLimitType then
set SettingLimitType_L to my splitStringToList(SettingLimitType, "&")
repeat with sltCtr from 1 to (count of SettingLimitType_L)
set theLimitType to SettingLimitType_L's item sltCtr
if "boolean" = theLimitType then
set settingLimitList to {true, false}
set hasSettingLimitList to true
else if "InMin" = theLimitType then
set hasInclusiveMin to true
set hasFreeInput to true
set settingMin to my deReference((get item 1 of theSettingRecord's s_Limit_L), valueCLassName)
else if "InMax" = theLimitType then
set hasInclusiveMax to true
set hasFreeInput to true
set settingMax to my deReference((get item -1 of theSettingRecord's s_Limit_L), valueCLassName)
else if "ExMin" = theLimitType then
set hasExclusiveMin to true
set hasFreeInput to true
set settingMin to my deReference((get item 1 of theSettingRecord's s_Limit_L), valueCLassName)
else if "ExMax" = theLimitType then
set hasExclusiveMax to true
set hasFreeInput to true
set settingMax to my deReference((get item -1 of theSettingRecord's s_Limit_L), valueCLassName)
else if "Free" = theLimitType then
set hasFreeInput to true
else if "List" = theLimitType then
set settingLimitList to my deReference((get theSettingRecord's s_Limit_L), valueCLassName)
set hasSettingLimitList to true
if ("text" = valueCLassName) and (SettingLimitType_L contains "OkSpace") then ¬
set settingLimitList to my splitStringToList(("\"" & my joinListToString(settingLimitList, "\",\"") & "\""), ",")
else if "OkSpace" = theLimitType then
set hasOkSpace to true -- add quotation marks to the text string or list of strings
if ("text" = valueCLassName) then
set hasQuotation to true
if valueIsList then
set settingValue to my splitStringToList(("\"" & my joinListToString(settingValue, "\",\"") & "\""), ",")
else
set settingValue to "\"" & settingValue & "\""
end if
end if
else if "NoSpace" = theLimitType then
set hasNoSpace to true -- add a space to the SettingBlockList
if ("text" = valueCLassName) then
if hasSettingBlockList then
if SettingBlockList does not contain " " then set end of SettingBlockList to " "
else
set hasSettingBlockList to true
set SettingBlockList to {" "}
end if
end if
else
set ParseError to true -- minor errors
set end of ParseErrorList to ("Setting \" " & settingName & "\" has an unexpected Setting Limit type: \"" & theLimitType & "\"")
end if
end repeat
end if
local blockLTSpaces
set blockLTSpaces to ("text" = valueCLassName) and (not hasNoSpace) and (not hasOkSpace) and (not (hasSettingBlockList and (SettingBlockList contains " "))) -- blockLT is the default if no other space control
if (not hasFreeInput) then -- errors that will cause a failure to edit
if (not hasSettingLimitList) then return {ParseFail:true, ParseErrorString:("Setting \"" & settingName & "\" is configured with free input disabled and with list input disabled")}
if (0 = (count of settingLimitList)) then return {ParseFail:true, ParseErrorString:("Setting \"" & settingName & "\" is configured with free input disabled and has an empty Choose List")}
end if
end ignoring
return {ParseFail:false, ParseError:ParseError, ParseErrorList:ParseErrorList, settingName:settingName, settingValue:settingValue, valueCLassName:valueCLassName, valueIsList:valueIsList, blockLTSpaces:blockLTSpaces, hasSettingBlockList:hasSettingBlockList, SettingBlockList:SettingBlockList, hasFreeInput:hasFreeInput, hasInclusiveMin:hasInclusiveMin, hasInclusiveMax:hasInclusiveMax, hasExclusiveMin:hasExclusiveMin, hasExclusiveMax:hasExclusiveMax, hasSettingLimitList:hasSettingLimitList, settingMin:settingMin, settingMax:settingMax, settingLimitList:settingLimitList, hasQuotation:hasQuotation, hasNoSpace:hasNoSpace, hasOkSpace:hasOkSpace}
end guiParseSettingRecord
on guiSelectSetting(theGuiData)
## General Purpose handler that enables the user to select the setting to be edited
global debugLogEnable
local usercancelled, userDone, settings_choose_List, settings_choose_List, ChooseResult, selectedSetting, settingID, numSelectedSetting, theSelected_Setting, theSettingValueS
local separatorLine, xor
--local settingsList
--set settingsList to theGuiData's guiRecordList
set {usercancelled, userDone, separatorLine} to {false, false, "-----"}
repeat while userDone is false
copy {} to settings_choose_List
repeat with recordCtr from 1 to (count of theGuiData's guiRecordList)
## set theSetting_r to theGuiData's guiRecordList's record recordCtr
## copy (get theGuiData's guiRecordList's record recordCtr) to theSetting_r
set xor to true
try
if (true = (contents of theGuiData's guiRecordList's record recordCtr's s_Invert)) then set xor to false
end try
if (xor = (contents of theGuiData's guiRecordList's record recordCtr's s_Active)) and (true = contents of theGuiData's guiRecordList's record recordCtr's s_UserSet) then
if ((contents of theGuiData's guiRecordList's record recordCtr's s_Class) contains "list") then
if ((contents of theGuiData's guiRecordList's record recordCtr's s_Class) contains "text") then
if 0 < (count of (get contents of theGuiData's guiRecordList's record recordCtr's s_Value)) then
copy ("{\"" & (my joinListToString((get contents of theGuiData's guiRecordList's record recordCtr's s_Value), "\",\"")) & "\"}") to theSettingValueS
else
set theSettingValueS to "{}"
end if
else
copy ("{" & (my joinListToString((get contents of theGuiData's guiRecordList's record recordCtr's s_Value), ",")) & "}") to theSettingValueS
end if
else if ("text" = (contents of theGuiData's guiRecordList's record recordCtr's s_Class)) then
copy ("\"" & (get contents of theGuiData's guiRecordList's record recordCtr's s_Value) & "\"") to theSettingValueS
else
copy ("" & (get contents of theGuiData's guiRecordList's record recordCtr's s_Value)) to theSettingValueS
end if
copy (((get contents of theGuiData's guiRecordList's record recordCtr's s_ID) as text) & ": " & (get contents of theGuiData's guiRecordList's record recordCtr's s_Name) & ": " & theSettingValueS) to the end of settings_choose_List
else if (true = (contents of theGuiData's guiRecordList's record recordCtr's s_Active)) and (missing value = contents of theGuiData's guiRecordList's record recordCtr's s_UserSet) then
copy separatorLine to the end of settings_choose_List
end if
end repeat
set ChooseResult to {separatorLine}
repeat while separatorLine = ChooseResult's first item
if theGuiData's guiParams's enableFastGui then
set ChooseResult to (get choose from list settings_choose_List with title "Settings for " & theGuiData's guiParams's Script_Title with prompt "Select an Item to Edit or no Item if Done" cancel button name "Cancel" OK button name "Change/Done" with empty selection allowed)
if ChooseResult = false then -- user has cancelled
set usercancelled to true
exit repeat -- exit from this repeat loop
else if 0 = (count of ChooseResult) then -- user is done editting
set userDone to true
exit repeat -- exit from this repeat loop
end if
else
set ChooseResult to (get choose from list settings_choose_List with title "Settings for " & theGuiData's guiParams's Script_Title with prompt "Select an Item to Edit" cancel button name "Done" OK button name "Edit" without empty selection allowed)
if ChooseResult = false then -- user is done editting
set userDone to true
exit repeat -- exit from this repeat loop
end if
end if
end repeat
if userDone or usercancelled then exit repeat --exit from the main repeat loop
## without empty selection allowed
set selectedSetting to item 1 of ChooseResult
set settingID to get ((item 1 of (my splitStringToList(selectedSetting, ":"))) as integer)
set numSelectedSetting to false
repeat with recordCtr from 1 to (count of theGuiData's guiRecordList)
if settingID = (theGuiData's guiRecordList's record recordCtr's s_ID) then
set numSelectedSetting to 0 + recordCtr
set settingFound to true
exit repeat
end if
end repeat
if false = numSelectedSetting then error my loqqed_Error_Halt5("SW Error 1: Unable to find setting #" & settingID)
guiEditSetting(theGuiData, numSelectedSetting)
end repeat
return usercancelled
end guiSelectSetting
on guiEditSetting(theGuiData, numSelectedSetting)
## General Purpose handler that provides a user interface to edit one setting
global debugLogEnable
ignoring case -- for this entire handler
local theRecord
set theRecord to guiParseSettingRecord(get theGuiData's guiRecordList's record numSelectedSetting)
if theRecord's ParseFail then error my loqqed_Error_Halt5((get theRecord's ParseErrorString))
if debugLogEnable and theRecord's ParseError then my loqThis(2, false, (get theRecord's ParseErrorList))
local theSettingName, theValueCLassName, valueIsList, theSettingLimitList, hasSettingLimitList, hasFreeInput, theSettingBlockList, hasSettingBlockList, blockLTSpaces, theSettingMin, hasInclusiveMin, theSettingMax, hasInclusiveMax, hasExclusiveMin, hasExclusiveMax, hasQuotation
tell theRecord
set {theSettingName, theSettingValue, theValueCLassName, valueIsList, theSettingLimitList, hasSettingLimitList, hasFreeInput, theSettingBlockList, hasSettingBlockList, blockLTSpaces, theSettingMin, hasInclusiveMin, hasExclusiveMin, theSettingMax, hasInclusiveMax, hasExclusiveMax, hasQuotation} to ¬
{(get its settingName), (get its settingValue), (get its valueCLassName), (get its valueIsList), (get its settingLimitList), (get its hasSettingLimitList), (get its hasFreeInput), (get its SettingBlockList), (its hasSettingBlockList), (its blockLTSpaces), (its settingMin), (its hasInclusiveMin), (its hasExclusiveMin), (its settingMax), (its hasInclusiveMax), (its hasExclusiveMax), (its hasQuotation)}
end tell
local settingHelpPrompt, SettingBlockString
try
set settingHelpPrompt to (get (theGuiData's guiRecordList's record numSelectedSetting's s_Help) as text)
if 0 = (get length of settingHelpPrompt) then
set settingHelpPrompt to ""
else
set settingHelpPrompt to return & " (" & settingHelpPrompt & ")"
end if
on error
set settingHelpPrompt to ""
end try
if hasSettingBlockList then
set SettingBlockString to return & "Blocked characters: \"" & my joinListToString(theSettingBlockList, ("\", \"")) & "\""
else
set SettingBlockString to ""
end if
if debugLogEnable then
my loqThis(4, false, ¬
{"settingName: " & theSettingName, "valueCLassName: " & theValueCLassName, "valueIsList: " & valueIsList, "hasFreeInput: " & hasFreeInput})
my loqThis(4, false, ¬
{"hasSettingLimitList: " & hasSettingLimitList, "hasSettingBlockList: " & hasSettingBlockList, "blockLTSpaces: " & blockLTSpaces, "hasQuotation: " & hasQuotation})
my loqThis(4, false, ¬
{"hasInclusiveMin: " & hasInclusiveMin, "hasInclusiveMax: " & hasInclusiveMax, "hasExclusiveMin: " & hasExclusiveMin, "hasExclusiveMax: " & hasExclusiveMax})
end if
local usercancelled, hasNewSettingValue, chooseSettingValueList, buttonList
local settingAddTitle, settingChooseTitle, settingChoosePrompt, settingEditPrompt
local newSettingEntry, ChooseResult, dialog_result, dialogResultList, theValueString, skipListInput
local errorText, errorNumber
## Setup local variables
set usercancelled to false
set hasNewSettingValue to false
set chooseSettingValueList to missing value
set skipListInput to false
#### Setup is complete
if (not valueIsList) then
if theGuiData's guiParams's enableFastGui and (theValueCLassName = "Boolean") then
set contents of theGuiData's guiRecordList's record numSelectedSetting's s_Value to (get not theSettingValue)
set hasNewSettingValue to true
else
set settingAddTitle to "Enter a New Value"
set settingChooseTitle to "Select a New Value"
copy ("Setting: " & (get theSettingName as text) & (get settingHelpPrompt as text) & SettingBlockString) to settingEditPrompt
if hasFreeInput and (not (theGuiData's guiParams's enableFastGui and hasSettingLimitList)) then
if (hasInclusiveMin or hasInclusiveMax or hasExclusiveMin or hasExclusiveMax) then set settingEditPrompt to settingEditPrompt & return
if hasInclusiveMin then set settingEditPrompt to settingEditPrompt & "Minimum: ≥" & my deReference(theSettingMin, theValueCLassName)
if hasExclusiveMin then set settingEditPrompt to settingEditPrompt & "Minimum: >" & my deReference(theSettingMin, theValueCLassName)
if hasInclusiveMin or hasExclusiveMin then set settingEditPrompt to settingEditPrompt & "; "
if hasInclusiveMax then set settingEditPrompt to settingEditPrompt & "Maximum: ≤" & my deReference(theSettingMax, theValueCLassName)
if hasExclusiveMax then set settingEditPrompt to settingEditPrompt & "Maximum: <" & my deReference(theSettingMax, theValueCLassName)
if hasSettingLimitList then
set buttonList to {"Select", "Cancel Editing", "Skip List Input"}
else
set buttonList to {"Select", "Cancel Editing"}
end if
repeat while not hasNewSettingValue
set skipListInput to false
set dialog_result to display dialog settingEditPrompt with title settingAddTitle default answer theSettingValue buttons buttonList
if "Cancel Editing" = (get dialog_result's button returned) then
set usercancelled to true
set hasNewSettingValue to false
exit repeat
end if
try
if 0 < (length of text returned of dialog_result) then
set newSettingEntry to my deReference((text returned of dialog_result), theValueCLassName)
if hasSettingBlockList then set newSettingEntry to my replaceText(newSettingEntry, theSettingBlockList, "")
if blockLTSpaces then set newSettingEntry to my removeLeadingTrailingSpaces(newSettingEntry)
if hasQuotation then set newSettingEntry to "\"" & (my replaceText(newSettingEntry, "\"", "")) & "\""
set hasNewSettingValue to true
else if 0 = (length of text returned of dialog_result) and ("text" = theValueCLassName) then
if hasQuotation then
set newSettingEntry to "\"" & "\""
else
set newSettingEntry to ""
end if
set hasNewSettingValue to true
else
error
end if
on error
display dialog (settingEditPrompt & return & "Unable to convert \"" & (get text returned of dialog_result) & "\" to an " & theValueCLassName) with title "Press OK to try again" buttons {"OK"} default button "OK"
set hasNewSettingValue to false
end try
if hasNewSettingValue and ¬
((hasInclusiveMin and (newSettingEntry < theSettingMin)) or (hasInclusiveMax and (newSettingEntry > theSettingMax)) or ¬
(hasExclusiveMin and (newSettingEntry ≤ theSettingMin)) or (hasExclusiveMax and (newSettingEntry ≥ theSettingMax))) then
set hasNewSettingValue to false
display dialog (settingEditPrompt & return & "The value " & newSettingEntry & " is below the Min or above the Max") with title "Press OK to try again" buttons {"OK"} default button "OK"
end if
if hasNewSettingValue and ("Skip List Input" = (get button returned of dialog_result)) then set skipListInput to true
end repeat
end if
if hasSettingLimitList and (not usercancelled) and (not skipListInput) then
set chooseSettingValueList to (get contents of theSettingLimitList)
if chooseSettingValueList does not contain theSettingValue then set end of chooseSettingValueList to theSettingValue
if hasNewSettingValue and (chooseSettingValueList does not contain newSettingEntry) then set end of chooseSettingValueList to newSettingEntry
if not hasNewSettingValue then
set chooseDefaults to theSettingValue
else
set chooseDefaults to newSettingEntry
end if
set ChooseResult to (get choose from list chooseSettingValueList with prompt settingEditPrompt ¬
with title settingChooseTitle OK button name "Select" cancel button name "Cancel Editing" default items chooseDefaults)
if ChooseResult = false then
set usercancelled to true
set hasNewSettingValue to false
else
if (0 ≤ (count of ChooseResult)) then set newSettingEntry to my deReference((get item 1 of ChooseResult), theValueCLassName)
set hasNewSettingValue to true
end if
end if
if hasNewSettingValue then
if hasQuotation then set newSettingEntry to my replaceText(newSettingEntry, "\"", "")
set contents of theGuiData's guiRecordList's record numSelectedSetting's s_Value to newSettingEntry
end if
end if
if hasNewSettingValue then
try
theGuiData's guiRecordList's record numSelectedSetting's s_Script's resolve()
on error errorText number errorNumber
if -1728 = errorNumber then
my loqThis(4, false, "Did not find " & theSettingName & "'s Script: " & errorText)
else
my loqThis(2, false, "Got an error regarding " & theSettingName & "'s resolve handler: " & errorText & " (" & errorNumber & ")")
end if
end try
end if
else --- handle a list
## Initiialise local variables
set settingAddTitle to "Enter a New Value"
set settingChooseTitle to "Select Values"
set settingChoosePrompt to "Setting: " & theSettingName & settingHelpPrompt & return & "Empty selection allowed" & return & "(cmd-click deselects)"
set settingEditPrompt to "Setting: " & theSettingName & settingHelpPrompt & return & "(use ';' to make a list)" & SettingBlockString
set chooseSettingValueList to (get contents of theSettingValue)
set newSettingEntry to {}
set hasNewSettingValue to false
set skipListInput to false
if hasSettingLimitList then
repeat with theValueString in theSettingLimitList
if chooseSettingValueList does not contain (contents of theValueString) then copy (contents of theValueString) to the end of chooseSettingValueList
end repeat
end if
if hasFreeInput and (not (theGuiData's guiParams's enableFastGui and (0 < (count of chooseSettingValueList)))) then
if 0 < (count of chooseSettingValueList) then
set buttonList to {"OK", "Cancel Editing", "Skip List Input"}
else
set buttonList to {"OK", "Cancel Editing"}
end if
set dialog_result to display dialog settingEditPrompt with title settingAddTitle default answer "" buttons buttonList
if "Cancel Editing" = (get dialog_result's button returned) then set usercancelled to true
if (not usercancelled) then
set dialogResultList to my splitStringToList((get text returned of dialog_result), ";")
if hasSettingBlockList then copy my removeTextFromList(dialogResultList, theSettingBlockList) to dialogResultList
if blockLTSpaces then copy my removeLeadingTrailingSpaces(dialogResultList) to dialogResultList
if hasQuotation then copy (my removeTextFromList(dialogResultList, "\"")) to dialogResultList
if 0 = (length of text returned of dialog_result) then
set newSettingEntry to {}
set hasNewSettingValue to true
else
if hasQuotation then copy my splitStringToList(("\"" & my joinListToString(dialogResultList, "\",\"") & "\""), ",") to dialogResultList
repeat with theValueString in dialogResultList
if (0 < (get length of theValueString)) and (newSettingEntry does not contain (contents of theValueString)) then
copy (contents of theValueString) to the end of newSettingEntry
if chooseSettingValueList does not contain (contents of theValueString) then copy (contents of theValueString) to the end of chooseSettingValueList
set hasNewSettingValue to true
end if
end repeat
end if
if ("Skip List Input" = (get button returned of dialog_result)) then set skipListInput to true
end if
end if
if (not usercancelled) and (not skipListInput) and (0 < (get count of chooseSettingValueList)) then
set ChooseResult to (get choose from list chooseSettingValueList with prompt settingChoosePrompt ¬
with title settingChooseTitle OK button name "Select" cancel button name "Cancel Editing" default items (theSettingValue & newSettingEntry) with empty selection allowed and multiple selections allowed)
if ChooseResult = false then
set usercancelled to true
set hasNewSettingValue to false -- might have been set to true in free input section
else
copy (get ChooseResult as list) to newSettingEntry
set hasNewSettingValue to true
end if
end if
if hasNewSettingValue and not usercancelled then
if hasQuotation then set newSettingEntry to my removeTextFromList(newSettingEntry, "\"")
set the contents of theGuiData's guiRecordList's record numSelectedSetting's s_Value to {}
copy (contents of newSettingEntry) to contents of theGuiData's guiRecordList's record numSelectedSetting's s_Value
try
theGuiData's guiRecordList's record numSelectedSetting's s_Script's resolve()
on error errorText number errorNumber
if -1728 = errorNumber then
my loqThis(4, false, "Did not find " & theSettingName & "'s Script: " & errorText)
else
my loqThis(2, false, "Got an error regarding " & theSettingName & "'s resolve handler: " & errorText & " (" & errorNumber & ")")
end if
end try
end if
end if
end ignoring
return -- the only exit
end guiEditSetting
on getScriptTitle(script_path, removeExtension, replacePeriod)
## if removeExtension the file extension and period are removed
## Remaining "." are replaced by the replacePeriod characters
set script_path to script_path as string
set astid to AppleScript's text item delimiters
try
set AppleScript's text item delimiters to ":"
if (script_path ends with ":") then
set Script_Title to text item -2 of script_path
else
set Script_Title to text item -1 of script_path
end if
set lastTextItem to -1
if removeExtension and (Script_Title contains ".") then set lastTextItem to -2
set AppleScript's text item delimiters to "."
set Script_Title to text items 1 thru lastTextItem of Script_Title
if false ≠ replacePeriod then set AppleScript's text item delimiters to replacePeriod
set Script_Title to Script_Title as text
end try
set AppleScript's text item delimiters to astid
if 0 = (get length of Script_Title) then error "getScriptTitle: Script Title is empty"
return Script_Title
end getScriptTitle
on findTargetFolder(targetFolderParent_a, targetFolderName, debugLogLevel, enableCreate)
## targetFolderParent_a can be an alias or a string
local targetFolder_p, targetFolder_a
local errorString1, errorNumber1, errorString2, errorNumber2
try
set targetFolder_p to (POSIX path of targetFolderParent_a) & targetFolderName
if 1 ≤ debugLogLevel then tell me to log " Initializing Folder \"" & targetFolder_p & "\""
set targetFolder_a to (get alias POSIX file targetFolder_p)
if 2 ≤ debugLogLevel then tell me to log "Folder \"" & targetFolderName & "\" exists"
on error errorString1 number errorNumber1
try
if (errorNumber1 = -1728) then
if enableCreate then
tell application "Finder" to set targetFolder_a to (make new folder at targetFolderParent_a with properties {name:targetFolderName}) as alias
if 2 ≤ debugLogLevel then tell me to log "Folder \"" & targetFolderName & "\" was created"
else
if 1 ≤ debugLogLevel then tell me to log "Could not find folder \"" & targetFolder_p & "\"."
set targetFolder_a to false
end if
else
error errorString1 number errorNumber1
end if
on error errorString2 number errorNumber2
error ("findTargetFolder() had error " & errorNumber2 & " \"" & errorString2 & "\".") number errorNumber2
end try
end try
return targetFolder_a
end findTargetFolder
on stdDecimalVersionNumber(theVersion_S, numDigits)
## General purpose handler for creating a version number with a fixed number of digits per group
## if numDigits is 0 then the number of digits per group is chosen automatically
## if numDigits is < 0 then the number of digits per group is chosen automatically, but has a minimum value of (-numDigits)
local theVersionNumber, theVersion_LS, theDotVersion_LS, groupMult, maxDigits
local thisGroupMult, thisGroup_S
set {theVersion_LS, numDigits} to {(splitStringToList(replaceText(removeLeadingTrailingSpaces((theVersion_S as text)), " ", "."), ".")), (numDigits as integer)}
set theVersionNumber to (item 1 of theVersion_LS) as integer
if 1 < (get count of theVersion_LS) then
set {hasDotVersions, theDotVersion_LS} to {true, (items 2 thru -1 of theVersion_LS)}
if numDigits < 1 then
set maxDigits to -numDigits
repeat with thisGroup_S in theDotVersion_LS
if (length of thisGroup_S) > maxDigits then set maxDigits to (length of thisGroup_S)
end repeat
else
set maxDigits to numDigits
repeat with thisGroup_S in theDotVersion_LS
if numDigits < (length of thisGroup_S) then error "The digit group \"" & thisGroup_S & "\" has more than " & numDigits & " digits"
end repeat
end if
set {thisGroupMult, groupMult} to {1, (10 ^ maxDigits)}
repeat with thisGroup_S in theDotVersion_LS
set thisGroupMult to thisGroupMult / groupMult
set theVersionNumber to theVersionNumber + (thisGroup_S as integer) * thisGroupMult
end repeat
else
set hasDotVersions to false
if numDigits > 0 then set {maxDigits, groupMult} to {numDigits, (10 ^ numDigits)}
if numDigits = 0 then set {maxDigits, groupMult} to {1, 10}
if numDigits < 0 then set {maxDigits, groupMult} to {(-numDigits), (10 ^ (-numDigits))}
end if
return {stdVersionNumber:theVersionNumber, lengthVersionGroup:maxDigits, multVersionGroup:groupMult, hasDotVersions:hasDotVersions}
end stdDecimalVersionNumber
on compareVersion(testVersion_S, minVersion_S, maxVersion_S)
## General purpose handler for comparing versions
--local digitMult, testVersionNumber, minVersionNumber, maxVersionNumber, testVersion_LS, minVersion_LS, maxVersion_LS, groupsCount, group_ctr
--local testGroupsCount, minGroupsCount, maxGroupsCount, hasTestGroup, hasMinGroup, hasMaxGroup, testGroupVersion, minGroupVersion, maxGroupVersion
--local allBoundsPass, lowerBoundPass, upperBoundPass, lowerBoundFail, upperBoundFail, boundsCheckPass
if (text ≠ (get class of testVersion_S)) or (text ≠ (get class of minVersion_S)) or (text ≠ (get class of maxVersion_S)) then error "compareVersion() failed: Text inputs only"
set testVersion_LS to (splitStringToList(replaceText(removeLeadingTrailingSpaces(testVersion_S), " ", "."), "."))
set minVersion_LS to (splitStringToList(replaceText(removeLeadingTrailingSpaces(minVersion_S), " ", "."), "."))
set maxVersion_LS to (splitStringToList(replaceText(removeLeadingTrailingSpaces(maxVersion_S), " ", "."), "."))
set testGroupsCount to get count of testVersion_LS
set minGroupsCount to get count of minVersion_LS
set maxGroupsCount to get count of maxVersion_LS
set groupsCount to testGroupsCount
if groupsCount < minGroupsCount then set groupsCount to minGroupsCount
if groupsCount < maxGroupsCount then set groupsCount to maxGroupsCount
set {lowerBoundPass, lowerBoundFail, upperBoundPass, upperBoundFail, boundsCheckPass} to {false, false, false, false, false}
repeat with group_ctr from 1 to groupsCount
set hasTestGroup to (get group_ctr ≤ testGroupsCount)
set hasMinGroup to (get group_ctr ≤ minGroupsCount)
set hasMaxGroup to (get group_ctr ≤ maxGroupsCount)
if hasTestGroup then set testGroupVersion to 0 + (item group_ctr of testVersion_LS)
if hasMinGroup then set minGroupVersion to 0 + (item group_ctr of minVersion_LS)
if hasMaxGroup then set maxGroupVersion to 0 + (item group_ctr of maxVersion_LS)
if not boundsCheckPass then
if hasMaxGroup and hasMinGroup then
if maxGroupVersion > minGroupVersion then set boundsCheckPass to true
if maxGroupVersion < minGroupVersion then error "compareVersion() failed: Maximum version is less than minimum version"
else
set boundsCheckPass to true
end if
end if
if not (lowerBoundPass or lowerBoundFail) then
if hasMinGroup and hasTestGroup then
if testGroupVersion > minGroupVersion then set lowerBoundPass to true
if testGroupVersion < minGroupVersion then set lowerBoundFail to true
else if hasMinGroup then -- (and no testGroup) e.g. test 11 , min 11.1
set lowerBoundFail to true -- not symetric with upper bound behaviour
else if hasTestGroup then -- (and no minGroup) e.g. test 11.1, min 11 --> OK
set lowerBoundPass to true
end if
end if
if not (upperBoundPass or upperBoundFail) then
if hasMaxGroup and hasTestGroup then
if testGroupVersion < maxGroupVersion then set upperBoundPass to true
if testGroupVersion > maxGroupVersion then set upperBoundFail to true
else if hasMaxGroup then -- (and no testGroup) e.g. test 11.1 , max 11.1.2 or test 11 , max 11.1
set upperBoundPass to true -- not symetric with lower bound behaviour
else if hasTestGroup then --(and no maxGroup) e.g. test 11.1, max 11 --> pass
set upperBoundPass to true
end if
end if
end repeat
return {maxVersionPass:(not upperBoundFail), minVersionPass:(not lowerBoundFail)}
end compareVersion
on deReference(theItem, theclassName)
## reusult is a value, or list of values, of the specified class
## Enables data handling of multiple classes of items in the same code
if class = (get class of theclassName) then set theclassName to (get theclassName as text)
if list = (get class of (get theItem)) then
set theResult to {}
set cntItems to length of theItem
if (text = (get class of theclassName)) then
set hasClassList to false
else if (list = (get class of theclassName)) then
if (1 = (get length of theclassName)) then
set hasClassList to false
else if (cntItems = (get length of theclassName)) then
set hasClassList to true
else
error "deReference() can't handle a mismatch between number of items and number of classes"
end if
end if
if not hasClassList then copy (get theclassName as text) to thisclassName
repeat with item_ctr from 1 to cntItems
if hasClassList then copy (get (theclassName's item item_ctr) as text) to thisclassName
copy (get theItem's item item_ctr) to thisItem
if "boolean" = thisclassName then
set the end of theResult to false or (get thisItem as boolean)
else if "integer" = thisclassName then
set the end of theResult to 0 + (get thisItem as integer)
else if "text" = thisclassName then
set the end of theResult to "" & (get thisItem as text)
else if "real" = thisclassName then
set the end of theResult to 0.0 + (get thisItem as real)
else if "date" = thisclassName then
set the end of theResult to (get thisItem as date) + 0
else
error "deReference() can't handle class \"" & thisclassName & "\""
end if
end repeat
else
if "boolean" = theclassName then
set theResult to false or (get theItem as boolean)
else if "integer" = theclassName then
set theResult to 0 + (get theItem as integer)
else if "text" = theclassName then
set theResult to "" & (get theItem as text)
else if "real" = theclassName then
set theResult to 0.0 + (get theItem as real)
else if "date" = theclassName then
set theResult to (get theItem as date) + 0
else
error "deReference() can't handle class \"" & theclassName & "\" as " & (get class of theclassName)
end if
end if
return theResult
end deReference
on makeList(listLength, theElement) -- Note that the theElement can even be a List if listLength = 0 then return {} if listLength = 1 then return {theElement}
set theList to {theElement}
repeat while (count of theList) < listLength / 2
copy contents of theList to ListB
copy theList & ListB to theList
end repeat
copy contents of theList to ListB
return (theList & items 1 thru (listLength - (count of ListB)) of ListB)
end makeList
on splitStringToList(theString, theDelim)
set theList to {}
set astid to AppleScript's text item delimiters
try
set AppleScript's text item delimiters to theDelim
set theList to text items of theString
on error
set AppleScript's text item delimiters to astid
end try
set AppleScript's text item delimiters to astid
return theList
end splitStringToList
on joinListToString(theList, theDelim)
set theString to ""
set astid to AppleScript's text item delimiters
try
set AppleScript's text item delimiters to theDelim
set theString to theList as string
end try
set AppleScript's text item delimiters to astid
return theString
end joinListToString
on ConcatenatedRemovedLeadingTrailingSpaces(theString, separatorCharList)
## Attempts to handle any class of theString without crashing
## If the input is a list, the output is a string which is all trimmed strings concatenated
## If separatorCharList is a string, the first character is the main separator, the second and third characters are the start and end separators for a sub list
## If separatorCharList is a list, the first string is the main separator, the second and third strings are the start and end separators for a sub list
local newString, theSubString, resString, isFirstItem, mainSepChar, startSepChar, endSepChar, startSubSepChar, endSubSepChar
local thestringLen, hasTriggered, indexLow, indexHigh, errMess1
if (list = (get (class of theString))) then
set {resString, isFirstItem} to {"", true}
set {mainSepChar, startSubSepChar, endSubSepChar, countSepChars} to {"", "", "", (get length of separatorCharList)}
if 0 < countSepChars then
if text = (get class of separatorCharList) then
set mainSepChar to separatorCharList's text 1
if 2 ≤ countSepChars then set startSubSepChar to (get separatorCharList's text 2)
if 3 ≤ countSepChars then set endSubSepChar to separatorCharList's text 3
else if list = (get class of separatorCharList) then
set mainSepChar to separatorCharList's item 1
if 2 ≤ countSepChars then set startSubSepChar to (get separatorCharList's item 2)
if 3 ≤ countSepChars then set endSubSepChar to separatorCharList's item 3
end if
end if
repeat with theSubString in theString
set newString to ConcatenatedRemovedLeadingTrailingSpaces(theSubString, separatorCharList)
if 0 < (get length of newString) then
set {startSepChar, endSepChar} to {"", ""}
if (list = (get class of theSubString)) then set {startSepChar, endSepChar} to {startSubSepChar, endSubSepChar}
if not isFirstItem then set startSepChar to mainSepChar & startSepChar
set resString to resString & startSepChar & newString & endSepChar
if isFirstItem then set isFirstItem to false
end if
end repeat
return resString
else if (text = (get (class of theString))) then
set {thestringLen, hasTriggered} to {(get count of theString), false}
repeat with indexLow from 1 to thestringLen
set hasTriggered to (" " ≠ (get text indexLow of theString as text))
if hasTriggered then exit repeat
end repeat
if not hasTriggered then return ""
repeat with indexHigh from -1 to (-thestringLen) by -1
if " " ≠ (get text indexHigh of theString as text) then exit repeat
end repeat
return (text indexLow thru indexHigh of theString)
else
try
return (get theString as text)
on error errMess1
try
return (get name of theString)
on error
return errMess1
end try
end try
end if
end ConcatenatedRemovedLeadingTrailingSpaces
on removeLeadingTrailingSpaces(theString)
## handles both string and list input correctly
## handles just about any input without crashing
local newString, theSubString, thestringLen, hasTriggered, indexLow, indexHigh, errMess1
if (list = (get (class of theString))) then
set newString to {}
repeat with theSubString in theString
set the end of newString to removeLeadingTrailingSpaces(theSubString)
end repeat
return newString
else if (text = (get (class of theString))) then
set {thestringLen, hasTriggered} to {(get count of theString), false}
repeat with indexLow from 1 to thestringLen
set hasTriggered to (" " ≠ (get text indexLow of theString as text))
if hasTriggered then exit repeat
end repeat
if not hasTriggered then return ""
repeat with indexHigh from -1 to (-thestringLen) by -1
if " " ≠ (get text indexHigh of theString as text) then exit repeat
end repeat
return (text indexLow thru indexHigh of theString)
else
try
return (get theString as text)
on error errMess1
try
return (get name of theString)
on error
return errMess1
end try
end try
end if
end removeLeadingTrailingSpaces
on removeLeadingTrailingChars(theString, trimLeading, trimTrailing)
## handles both string and list input correctly
## if trimLeading or trimTrailing are boolean and true, then spaces are removed from the leading and trailing sides
## if trimLeading or trimTrailing are characters or strings, then these characters are removed from the leading and trailing sides
local enableTrimLeading, enableTrimTrailing, charTrimLeading, charTrimTrailing, charTrimLeadingIsString, charTrimTrailingIsString
local newString, theSubString, thestringLen, hasTriggered, indexLow, indexHigh, errMess1
if (list = (get (class of theString))) then
set newString to {}
repeat with theSubString in theString
set the end of newString to removeLeadingTrailingChars(theSubString, trimLeading, trimTrailing)
end repeat
return newString
else if (text = (get (class of theString))) then
set {enableTrimLeading, enableTrimTrailing} to {false, false}
if (trimLeading = true) then set {enableTrimLeading, charTrimLeading, charTrimLeadingIsString} to {true, " ", false}
if (text = (class of trimLeading)) then ¬
set {enableTrimLeading, charTrimLeading, charTrimLeadingIsString} to {true, trimLeading, (1 < (length of trimLeading))}
if (trimTrailing = true) then set {enableTrimTrailing, charTrimTrailing, charTrimTrailingIsString} to {true, " ", false}
if (text = (class of trimTrailing)) then ¬
set {enableTrimTrailing, charTrimTrailing, charTrimTrailingIsString} to {true, trimTrailing, (1 < (length of trimTrailing))}
set {thestringLen, indexLow, indexHigh, hasTriggered} to {(count of theString), 1, -1, false}
repeat with indexLow from 1 to thestringLen
if charTrimLeadingIsString then
set hasTriggered to (charTrimLeading does not contain (theString's text indexLow))
else
set hasTriggered to (charTrimLeading ≠ (theString's text indexLow))
end if
if hasTriggered then exit repeat
end repeat
if not hasTriggered then return ""
repeat with indexHigh from -1 to (-thestringLen) by -1
if charTrimTrailingIsString then
if (charTrimTrailing does not contain (theString's text indexHigh)) then exit repeat
else
if (charTrimTrailing ≠ (theString's text indexHigh)) then exit repeat
end if
end repeat
if ((thestringLen + 1 + indexHigh) < indexLow) then return ""
return (text indexLow thru indexHigh of theString)
else
try
return removeLeadingTrailingChars((get theString as text), trimLeading, trimTrailing)
on error errMess1
try
return removeLeadingTrailingChars((get name of theString), trimLeading, trimTrailing)
on error
return errMess1
end try
end try
end if
end removeLeadingTrailingChars
on removeTextFromList(theList, theBadChar)
## theBadChar may be a list of strings or characters, whereupon those strings will be removed
set theDelim to character id 60000 -- obscure character chosen for the low likelihood of its appearance
set astid to AppleScript's text item delimiters
try
set AppleScript's text item delimiters to theDelim
set theString to theList as string
set AppleScript's text item delimiters to theBadChar
set text_list to every text item of theString
set AppleScript's text item delimiters to ""
set cleanText_S to the text_list as string
set AppleScript's text item delimiters to theDelim
set theList to text items of cleanText_S
end try
set AppleScript's text item delimiters to astid
return theList
end removeTextFromList
on removeItemFromList(theList, theBadItem)
## theBadChar may be a list of strings or characters, whereupon those strings will be removed
local theDelim, theString, text_list, cleanText_S, errorText, astid
set theDelim1 to character id 60000 -- obscure characters chosen for the low likelihood of its appearance
set theDelim2 to character id 60001
set astid to AppleScript's text item delimiters
try
set AppleScript's text item delimiters to {theDelim2 & theDelim1}
set theString to theDelim1 & (theList as string) & theDelim2
set AppleScript's text item delimiters to theDelim1 & theBadItem & theDelim2
set text_list to every text item of theString
set AppleScript's text item delimiters to ""
set cleanText_S to text_list as string
if 2 < (count of cleanText_S) then
set cleanText_S to text 2 thru -2 of (text_list as string)
else
set cleanText_S to ""
end if
set AppleScript's text item delimiters to {theDelim2 & theDelim1}
set theList to text items of cleanText_S
end try
set AppleScript's text item delimiters to astid
return theList
end removeItemFromList
on replaceText(this_text, search_string, replacement_string) set astid to AppleScript's text item delimiters try set AppleScript's text item delimiters to the search_string set the item_list to every text item of this_text set AppleScript's text item delimiters to the replacement_string set this_text to the item_list as string end try set AppleScript's text item delimiters to astid return this_text end replaceText
on getIndexOfStrict(theItem, theList)
set theDelim to character id 60000 -- obscure character chosen for the low likelihood of its appearance
set theReturnToken to character id 60001
set theSearchItem to return & replaceText(theItem, return, theReturnToken) & return
set tempString to joinListToString(theList, theDelim)
set tempString to replaceText(tempString, return, theReturnToken)
set theSearchList to return & replaceText(tempString, theDelim, return) & return
try
-1 + (count (paragraphs of (text 1 thru (offset of theSearchItem in theSearchList) of theSearchList)))
on error
0
end try
end getIndexOfStrict
on roundToQuantum(thisValue, quantum)
return (round (thisValue / quantum) rounding to nearest) * quantum
end roundToQuantum
on roundDecimals(n, numDecimals)
set x to 10 ^ numDecimals
tell n * x to return (it div 0.5 - it div 1) / x
end roundDecimals
on MSduration(firstTicks, lastTicks)
## returns duration in ms
## inputs are durations, in seconds, from GetTick's Now()
return (round (10000 * (lastTicks - firstTicks)) rounding to nearest) / 10
end MSduration
on GetTick_Now()
## returns duration in seconds since since 00:00 January 2nd, 2000 GMT, calculated using computer ticks
script GetTick
property parent : a reference to current application
use framework "Foundation" --> for more precise timing calculations
on Now()
return (current application's NSDate's timeIntervalSinceReferenceDate) as real
end Now
end script
return GetTick's Now()
end GetTick_Now
on noValue()
end noValue
on isARef(objectToBeTested)
try
objectToBeTested as reference
return true
on error
return false
end try
end isARef
on summarizeList(theList, numWords, nullString) local safetyCtr, theListLength, summaryList, theItem, newString set safetyCtr to 0 set theListLength to count of theList set summaryList to {} repeat while 0 < (count of theList) set safetyCtr to safetyCtr + 1 if theListLength < safetyCtr then error set theItem to item 1 of theList set theList to removeItemFromList(theList, theItem) if 0 = (count of theItem) then if nullString and summaryList does not contain theItem then copy theItem to end of summaryList else if 0 = numWords then if summaryList does not contain theItem then copy theItem to end of summaryList else if 1 = numWords then set newString to word 1 of theItem if summaryList does not contain newString then copy newString to end of summaryList else set lastWord to count of words of theItem if numWords < lastWord then set lastWord to numWords set newString to joinListToString((words 1 thru lastWord of theItem), " ") if summaryList does not contain newString then copy newString to end of summaryList end if end repeat return summaryList end summarizeList
on ln(x, y)
## Y is approximately the digits of precision
## https://math.stackexchange.com/questions/75074/an-alternative-way-to-calculate-logx
## Chose this approach as it is faster the do shell Unix call
## prescaling
set e3 to 20.085536923188
set e3i to 0.049787068368
set v to 0
if x > e3 then
repeat while x > e3
set x to x / e3
set v to v + 3
end repeat
else if x < e3i then
repeat while x < e3i
set x to x * e3
set v to v - 3
end repeat
end if
set z to 10 ^ -y
set ak to (1 + x) / 2
set bk to x ^ 0.5
set ck to ak + bk
repeat 100 times -- about 9 - 10 times for 6 figure accuracy
set ak to (ak + bk) / 2
set bk to (ak * bk) ^ 0.5
set w to 1 - ((ak + bk) / ck) -- fractional change
if w < 0 then set w to -w
if w < z then exit repeat
set ck to ak + bk
end repeat
return v + 2 * (x - 1) / (ak + bk)
end ln
Please merge
First try of my improved framework. Includes a GUI to change script parametrs, the ability to store and read Script settings from a .plist file, and the ability to store found files in a user collection. First contribution, lets see how this goes.