Script for managing Stacks

Discussion regarding all scripting related questions
Please DO NOT post to this thread anything that is not directly related to scripting of Capture One.

Script for managing Stacks

Postby Eric Nepean » Sun Jun 23, 2019 9:23 pm

Capture One users have frequently mentioned in the forums that they seek a solution to group only selected variants, not every variant of some images.

Many of us also look for a DAM function known as "stacking", which groups a number of variants of different images. But not necessarily all the variants of those images.

Likely these features have been requested from Phase One, but no one knows if and when they might be delivered in Capture One Pro. Phase One understandably has a many business priorities to manage, and their R&D spend has to be managed (as does virtually every other commercial enterprise).

As an interim measure I provide this solution.

I define a Stacked Variants Album as a Smart Album which has a rule that selects variants with its own name in the Metadata.

In this partiicular implementation, the SVAs serch for their own name in the IPTC Subject Code field, which can convenintly hold a number of such strings. The SVA's names all start with "SVA_" so that a user can easily recognise them.

As one of my correpondents has pointed out, all of this can be done by hand without a script.

This script takes care of in a few seconds what will take one a few minutes by hand. And if you are interuppted by a client call or by family matters, the script won't forget or corrupt the data, it will still execute accurately.

The script will create SVA, and it will add selected variants from SVAs, and remove them. There is a fairly efficient dialog that will let the user choose the script's actions.

The entire script is too long for one posting, so I have boroken it into two parts. This is Part 1.
Part 2 is in the next posting.
Code: Select all
## Applescript to create a Stacked Variants Album (SVA) from Selected Variants
## Version 12.33 !! NO GUARANTEE OF SUPPORT !!  Best effort
## Copyright 2019 Eric Valk, Ottawa, Canada   Creative Commons License CC BY-SA    No Warranty.
-- Script Dialog
----"xxxx" adds the selected variants to the smart album "SVA_xxxx" 
----   Shortcuts are: "+","-","<",">" 
----   Shortcuts must be at the beginning
----   ">" adds clones of selected variants 
----   "<" adds clones of selected variants with adjustments reset 
----   "+" sets the SVA smart album using the title of the current collection: 
----   If the current collection is not an SVA, then the SVA will be "SVA_thisCollectionName" 
----   If the current collection is an SVA with no number then the SVA will be  "SVA_thisCollectionName_1" 
----   If the current collection is an SVA with a number then the number of the new SVA will be incremented   
----   "#nnn" at the end adds the variants to an SVA ending with "_nnn" 
----   "+*censored word*" adds the variants to  an SVA ending with "*censored word*" 
----   --- n are numbers, x are letters   
----   "-" removes selected variants instead of adding  them
----   The short cut "->" selects the removed variants in the parent project.
----   "?" generates a help menu with the short cut commands

-- *** Script Function and Definitions
-- An SVA is a Smart Album configured to select variants with its own name
-- The SVAs created here search in the variants' IPTC Subject Code field
-- The defaults is that the SVA is created  (if not present) in the "nearest" parent project or catalog
-- Variants are added to an SVA by adding the SVA's full name to their IPTC Subject Code field
-- Variants are removed from an SVA by removing the SVA's full name from their IPTC Subject Code field

-- *** User Protection
-- No variants are ever deleted. No collections are ever deleted
-- The variants IPTC Subject Code field is never set to ""

-- *** Use
-- select a Project, an album or smart album, select some variants
--  run this script - for starters, just use the default "+" in the dialog

-- ***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
-- 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 svaDialogText : "+"

set loqGUIsettings_L to my setupLoqqing5() -- setup the variables used for Loqqing system

## Values in this section are safe to change, within limits indicated. Support is likely but no commitment
##   Default values

set Loqqing's debugLogLevel to 0 --                     0...6 Values >1 result in increasing amounts of debug data that takes longer to report
set Loqqing's enableResultsFile to false --               (true/false)
set Loqqing's enableResultsByClipboard to false --         (true/false)
set Loqqing's enableNotifications to true --                  (true/false)  - enable notifications of errors and exceptions
set Loqqing's notificationsMaxDebug to 1 --            0...6  suggest not more than 1
set Loqqing's ResultsFileMaxDebug to 3 --               0...6  suggest not more than 2
set Loqqing's enableResultsByNotifications to true
set Loqqing's enableResultsByLoq to true
set Loqqing's enableResultsByDialog to false --         (true/false)
set Loqqing's maxDialogPercent to 85 --                   (0% to 100% of the monitor) The amount of data that triggers a dialog report

set nameSVA to "SVA"
set svaDialogSeparator to "#" --                        used to separate the counter part of the dialog
set charSeparator to "_" --                           used to separate the counter part of the SVA name
set svaMaxCount to 99 --                            maximum of 99 SVAs with the same basename  - can be set higher
set svaEnableFindParents to true --                      allow seraching outside the current collection
set svaGroupsAsParents to false --                      allow Groups as parent collections for SVAs

## ***** Not safe to change stuff below this line, unless you have some background in SW development.
## I generally won't help much if you change stuff below this line. I may explain the design intent.

set svaParentKindList to {"catalog", "project"}
if svaGroupsAsParents then set svaParentKindList to {"catalog", "group", "project"}


set debugLogEnable to (0 < Loqqing's debugLogLevel)

## Result reporting methods which are valid for this script
set Loqqing's gateResultsFile to true
set Loqqing's gateResultsByClipboard to true
set Loqqing's gateResultsNotification to true
set Loqqing's gateResultsDialog to true

## Reporting methods which are NOT valid for this script
-- disable the reporting method

-- disable user control

tell application "System Events" to set parent_name to name of current application
set Script_Title to (get name of me)
set Result_DocName to "CO_SVA_Report.txt"

set debugLogEnable to (0 < Loqqing's debugLogLevel)

set loqResultMethod to my InitializeLoqqing5(Result_DocName, Script_Title) -- Initialize the results logging system

set {minCOPversion, maxCOPversion} to {"12", "12"}
tell my validateCOP5(minCOPversion, maxCOPversion)
   if its hasErrors then error my loqqed_Error_Halt5(get its errorText)
   set {theAppName, copVersion} to {its theAppName, its copVersion}
end tell

tell application "Capture One 12" to set COPDocRef to get current document
tell my validateCOPdoc5(COPDocRef, {"Catalog"})
   if its hasErrors then error my loqqed_Error_Halt5(get its errorText)
   set {COPDocName, COPDocKind_s} to {its COPDocName, its COPDocKind_s}
end tell

tell my validateCOPcollections5(COPDocRef)
   set {selectedCollectionRef, kindSelectedCollection_s, nameSelectedCollection} to ¬
      {its selectedCollectionRef, its kindSelectedCollection_s, its nameSelectedCollection}
end tell

set {ruleSvaParts, criteriaSvaParts} to {{}, {}}
set ruleSvaParts to ruleSvaParts & "<?xml version=\"1.0\" encoding=\"UTF-8\"?><MatchOperator Kind=\"AND\"><MatchOperator Kind=\"AND\"><Condition Enabled=\"YES\"><Key>IB_S_CONTENT_SUBJECTCODE</Key><Operator>16</Operator><Criterion>"
set ruleSvaParts to ruleSvaParts & "</Criterion></Condition></MatchOperator></MatchOperator>"
set criteriaSvaParts to criteriaSvaParts & "<Key>IB_S_CONTENT_SUBJECTCODE</Key><Operator>16</Operator><Criterion>"
set criteriaSvaParts to criteriaSvaParts & "</Criterion>"
##Fix!!
tell application "Capture One 12" to set {refSelVariantsL, nameSelVariantsL, idSelVariantsL, svaSelVariantsL} to {it, name, id, content subject codes} of (variants whose selected is true)
set countSelectedVariants to count of idSelVariantsL

repeat
   set userCancelled to false
   try
      set svaDialogResult to display dialog "Name of Stacked Variants Album to add these variants to?" default answer svaDialogText
   on error number -128
      set userCancelled to true
      exit repeat
   end try
   if ((text returned of svaDialogResult) does not contain "?") then exit repeat
   try
      display dialog getHelp()
   on error number -128
      set userCancelled to true
      exit repeat
   end try
end repeat

if userCancelled then
   my loqThis(-1, true, "User cancelled the script")
else
   ##Process the dialog results
   set {svaDialogText, svaDialogControlChars, svaDialogHasArg, svaDialogArg, svaDialogArgLength} to {removeLeadingTrailingSpaces(text returned of svaDialogResult), "", false, "", 0}
   set {dialogTextLength, svaDialogArgPtr} to {(length of svaDialogText), (1 + (length of svaDialogText))}
   if debugLogEnable then my loqThis(2, false, "svaDialogText \"" & svaDialogText & "\"")
   
   if 0 = dialogTextLength then
      my loqThis(-1, true, "Name of Stacked Variants Album was empty and no shortcut")
   else
      ## Extract shortcuts
      set cmdL to {"-", "+", "<", ">"}
      repeat with textPtr from 1 to dialogTextLength
         if (cmdL does not contain item textPtr of svaDialogText) then
            set textPtr to textPtr - 1
            exit repeat
         end if
      end repeat
      set svaDialogArgPtr to textPtr + 1
      if textPtr > 0 then set svaDialogControlChars to (text 1 thru textPtr of svaDialogText)
      
      ## Extract Dialog Argument String
      if dialogTextLength ≥ svaDialogArgPtr then set svaDialogArg to text svaDialogArgPtr thru -1 of svaDialogText
      set svaDialogArgLength to length of svaDialogArg
      if svaDialogArgLength > 0 then set svaDialogHasArg to true
      if debugLogEnable then my loqThis(2, false, "svaDialogHasArg:" & svaDialogHasArg & "   svaDialogArg: \"" & svaDialogArg & "\"   svaDialogControlChars: \"" & svaDialogControlChars & "\"")
      
      ## Extract the Dialog Counter
      set {svaDialogHasCtr, svaDialogCtr} to {false, 0}
      
      if svaDialogArgLength ≥ 2 then
         if svaDialogHasArg then
            repeat with textPtr from svaDialogArgLength to 0 by -1
               if 0 = textPtr then exit repeat -- svaDialogArg is all numbers
               set textid to id of text textPtr of svaDialogArg
               if (textid < 48) or (textid > 57) then exit repeat
            end repeat
            if (textPtr > 0) and (svaDialogSeparator = (text textPtr of svaDialogArg)) then -- found a counter
               set {svaDialogHasCtr, svaDialogCtr} to {true, (0 + (text (textPtr + 1) thru -1 of svaDialogArg))}
               if (1 = textPtr) then
                  set {svaDialogHasArg, svaDialogArg, svaDialogArgLength} to {false, "", 0}
               else
                  set svaDialogArg to removeLeadingTrailingSpaces2((text 1 thru (textPtr - 1) of svaDialogArg), false, true)
                  set {svaDialogHasArg, svaDialogArgLength} to {not ("" = svaDialogArg), (length of svaDialogArg)}
               end if
               if debugLogEnable then my loqThis(2, false, "svaDialogHasArg:" & svaDialogHasArg & "   svaDialogArg: \"" & svaDialogArg & "\"")
            end if
         end if
      end if
      if debugLogEnable then my loqThis(2, false, "svaDialogHasCtr:" & svaDialogHasCtr & "    svaDialogCtr:" & svaDialogCtr)
      
      ##Determine Script Actions from the Shortcuts
      set {delSvaTag, cloneVariant, resetCloneAdj, svaFromCC, selDelVariants} to {false, false, false, false, false}
      if "" ≠ svaDialogControlChars then
         if (svaDialogControlChars contains "+") then
            set svaFromCC to true
            if (svaDialogControlChars contains ">") then set cloneVariant to true
            if (svaDialogControlChars contains "<") then set {cloneVariant, resetCloneAdj} to {true, true}
         end if
         if (svaDialogControlChars contains "-") then
            set delSvaTag to true
            set {cloneVariant, resetCloneAdj} to {false, false}
            if (svaDialogControlChars contains ">") then set selDelVariants to true
         end if
      end if
      if debugLogEnable then my loqThis(2, false, "delSvaTag: " & delSvaTag & ";  cloneVariant: " & cloneVariant & ";  resetCloneAdj: " & resetCloneAdj & ";  svaFromCC: " & svaFromCC & ";  selDelVariants: " & selDelVariants)
      
      ##Process the Current Collection Name
      set prefixSVA to nameSVA & charSeparator
      set {lenPrefixSVA, collNameIsSVA, lengthCollName} to {(length of prefixSVA), false, (get length of nameSelectedCollection)}
      ## Extract the Collection Base Name and determine if it is an SVA
      if (lenPrefixSVA < lengthCollName) and (prefixSVA = (get text 1 thru lenPrefixSVA of nameSelectedCollection)) then
         set collNameIsSVA to true -- Current collection has an SVA type name
         ##Get the current SVA collection's counter
         ## if the last characters of the name are the separator charactor, "_", followed by numbers, then it is an SVA counter.
         repeat with textPtr from lengthCollName to 1 by -1
            set textid to id of text textPtr of nameSelectedCollection
            if (textid < 48) or (textid > 57) then exit repeat
         end repeat
         set textPtr to (0 + textPtr) -- dereference the result
         if (textPtr < lengthCollName) and (charSeparator = (text textPtr of nameSelectedCollection)) then
            ##There is a counter
            set {collnCtr, collnIsCounted} to {(0 + (text (textPtr + 1) thru -1 of nameSelectedCollection)), true}
            set collnBaseName to text (lenPrefixSVA + 1) thru (textPtr - 1) of nameSelectedCollection -- separator character excluded
         else
            set {collnIsCounted, collnCtr, collnBaseName} to {false, 0, (text (lenPrefixSVA + 1) thru -1 of nameSelectedCollection)}
         end if
      else
         set {collNameIsSVA, collnIsCounted, collnCtr, collnBaseName} to {false, false, 0, nameSelectedCollection}
      end if
      if debugLogEnable then my loqThis(2, false, "collnBaseName: \"" & collnBaseName & "\"   collnIsCounted:" & collnIsCounted & "  collnCtr:" & collnCtr & " collNameIsSVA:" & collNameIsSVA)
      
      ## Determine the counter of the Target SVA
      if svaDialogHasCtr then
         set ctrTargetSva to svaDialogCtr
      else if collNameIsSVA then
         set ctrTargetSva to (collnCtr + 1)
         if delSvaTag then set ctrTargetSva to (collnCtr + 0)
      else
         set ctrTargetSva to 0
      end if
      
      ## Determine the base name of the Target SVA
      if svaFromCC then
         set baseNameTargetSva to (prefixSVA & collnBaseName & svaDialogArg)
      else if svaDialogHasArg then
         set baseNameTargetSva to (prefixSVA & svaDialogArg)
      else
         set baseNameTargetSva to (prefixSVA & collnBaseName)
      end if
      
      ## Determine the full name of the Target SVA
      copy baseNameTargetSva to nameTargetSva
      if 0 < ctrTargetSva then set nameTargetSva to nameTargetSva & charSeparator & (ctrTargetSva)
      if debugLogEnable then my loqThis(2, false, "nameTargetSva: \"" & nameTargetSva & "\"" & "  baseNameTargetSva:\"" & baseNameTargetSva & "\"   ctrTargetSva:" & ctrTargetSva)
      
      ##EXECUTION
      if delSvaTag then -- Remove Variants from an SVA
         if debugLogEnable then my loqThis(1, false, "Remove Variants from \"" & nameTargetSva & "\"")
         if (0 = countSelectedVariants) then
            my loqThis(-1, true, "No variants selected, none removed")
         else
            
            repeat with cntVariants from 1 to count of refSelVariantsL
               set {theSvaList_L, theVariantName} to {(my splitstringtolist((item cntVariants of svaSelVariantsL), ",")), (item cntVariants of nameSelVariantsL)}
               
               if (theSvaList_L contains nameTargetSva) then
                  tell application "Capture One 12" to tell (item cntVariants of refSelVariantsL) to set content subject codes to my joinlisttostring(my removeItemFromList(theSvaList_L, nameTargetSva), ",")
                  if debugLogEnable then my loqThis(1, false, "Removed tag for  \"" & nameTargetSva & "\" from Variant \"" & theVariantName & "\"")
               else
                  if debugLogEnable then my loqThis(1, false, "Tag for  \"" & nameTargetSva & "\" was not present in Variant \"" & theVariantName & "\"")
               end if
            end repeat
            
            if selDelVariants then
               set {svaEnableCreate, svaEnableCounting} to {false, false} -- find exactly this collection, don't create it
               set {theSvaRef, foundSva, refSvaParent} to findSvaCollection({selectedCollectionRef}, baseNameTargetSva, svaEnableCreate, svaEnableFindParents, svaEnableCounting, ctrTargetSva, svaMaxCount, charSeparator, svaParentKindList, ruleSvaParts, criteriaSvaParts)
               
               ##if foundSva then
               tell application "Capture One 12"
                  if "catalog" = my convertKindList(get kind of refSvaParent) then
                     tell COPDocRef to set current collection to collection "All Images"
                  else
                     tell COPDocRef to set current collection to refSvaParent
                  end if
                  deselect COPDocRef variants (variants)
                  repeat with theVariantID in idSelVariantsL
                     select COPDocRef variant (variant id ((contents of theVariantID) as string))
                  end repeat
               end tell
               ##end if
            end if
         end if
         
      else
         ## Add Variants to SVA, Create new SVA smart album if needed
         if debugLogEnable then my loqThis(1, false, "Searching for \"" & nameTargetSva & "\" to add variants")
         set svaEnableCreate to true
         if svaDialogHasCtr then -- Dialog specified the counter value
            set svaEnableCounting to false -- don't change or add a counter
         else
            set svaEnableCounting to true -- change or add a counter as needed
         end if
         
         set {theSvaRef, foundSva, refSvaParent} to findSvaCollection({selectedCollectionRef}, baseNameTargetSva, svaEnableCreate, svaEnableFindParents, svaEnableCounting, ctrTargetSva, svaMaxCount, charSeparator, svaParentKindList, ruleSvaParts, criteriaSvaParts)
         if not foundSva then
            my loqqed_Error_Halt5("Can't find or create the \"" & nameTargetSva & "\" smart album")
         else if (0 = countSelectedVariants) then
            my loqThis(-1, true, "No variants selected")
         else
            set nameTargetSva to name of theSvaRef
            if cloneVariant then
               tell application "Capture One 12"
                  repeat with cntVariants from 1 to count of refSelVariantsL
                     set theVariantRef to (clone variant (item cntVariants of refSelVariantsL) with additive select)
                     if resetCloneAdj then tell theVariantRef to reset adjustments
                     set {theSvaList, theVariantName} to {((item cntVariants of svaSelVariantsL) & ""), (item cntVariants of nameSelVariantsL)}
                     if ((my splitstringtolist(theSvaList, ",")) does not contain nameTargetSva) then
                        tell theVariantRef to set content subject codes to theSvaList & "," & nameTargetSva
                        if debugLogEnable then my loqThis(1, false, "Added tag for  \"" & nameTargetSva & "\" to Clone of Variant \"" & theVariantName & "\"")
                     else
                        if debugLogEnable then my loqThis(1, false, "Tag for  \"" & nameTargetSva & "\" already present in Variant \"" & theVariantName & "\" and its clone")
                     end if
                  end repeat
                  deselect COPDocRef variants refSelVariantsL
               end tell
            else
               repeat with cntVariants from 1 to count of refSelVariantsL
                  set {theSvaList, theVariantName} to {((item cntVariants of svaSelVariantsL) & ""), (item cntVariants of nameSelVariantsL)}
                  if ((my splitstringtolist(theSvaList, ",")) does not contain nameTargetSva) then
                     tell application "Capture One 12" to tell (item cntVariants of refSelVariantsL) to set content subject codes to theSvaList & "," & nameTargetSva
                     if debugLogEnable then my loqThis(1, false, "Added tag for  \"" & nameTargetSva & "\" to Variant \"" & theVariantName & "\"")
                  else
                     if debugLogEnable then my loqThis(1, false, "Tag for  \"" & nameTargetSva & "\" already present in Variant \"" & theVariantName & "\"")
                  end if
               end repeat
               
            end if
         end if
      end if
      
   end if
end if

################  The End ###############

on findSvaCollection(parentRefList, nameTargetColl, enableCreate, enableFindParents, enableCounting, InitCount, MaxCount, separatorChar, kindParentColl_L, saCreateRuleL, saRuleCriteriaL)
   ## Copyright 2019 Eric Valk, Ottawa, Canada   Creative Commons License CC BY-SA    No Warranty.
   ## General purpose handler  to find and create a collection, starting from some arbitrary collection
   ## Version June 22 2019
   
   global debugLogEnable
   local refParentColl, ptrCollRef, thisCollRef, thisCollKind_S, thisCollName, subCollNames, subCollRef, refFoundColl, hasFoundColl, debugText
   local collCreated, ctrCollName, nbrTargetCollName, collSearchMatch, collSearchSuccess, collSearchFail, kindTargetColl
   local foundSaRules, rulesPtr, saDefRule, refFoundSaColl
   
   copy (contents of item 1 of parentRefList) to thisCollRef
   if 1 < (count of parentRefList) then set enableFindParents to false
   -- saCreateRule, hasRuleCriteria,saDefRule
   -- No rules string -- length 80
   --first part is 38 long: "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
   set {saDefRule, lenRuleTest} to {"<?xml version=\"1.0\" encoding=\"UTF-8\"?><MatchOperator Kind=\"AND\"></MatchOperator>", 38}
   if not ((80 < (get length of item 1 of saCreateRuleL)) and ((text 1 thru lenRuleTest of saDefRule) = (text 1 thru lenRuleTest of item 1 of saCreateRuleL))) then error
   
   repeat with ptrCollRef in parentRefList
      set thisCollRef to contents of ptrCollRef
      
      tell application "Capture One 12" to tell thisCollRef to set {thisCollKind_S, thisCollName, refParentColl} to {my convertKindList(its kind), its name, it}
      set {ctrCollName, collSearchFail, collSearchMatch, collCreated, refFoundColl, hasFoundColl} to {0, false, false, false, {}, false}
      
      set nbrTargetCollName to nameTargetColl & ""
      if InitCount > 0 then set nbrTargetCollName to nameTargetColl & separatorChar & InitCount
      
      if debugLogEnable then
         tell application "Capture One 12" to tell thisCollRef to set subCollNames to name of its collections
         set debugText to "smart album \"" & nbrTargetCollName & "\"   in " & thisCollKind_S & " \"" & thisCollName & "\""
         my loqThis(2, false, return & "Find Collection: " & debugText & " with  Create: " & enableCreate & "   Parent Search: " & enableFindParents & "   Counter Increment: " & enableCounting)
         my loqThis(3, false, "SubCollections {" & my joinlisttostring(subCollNames, "; ") & "}")
      end if
      
      if (kindParentColl_L contains thisCollKind_S) and ({"album", "smart album"} does not contain thisCollKind_S) then
         if debugLogEnable then my loqThis(2, false, "Searching for the collection")
         set ctrCollName to (InitCount + 0)
         tell application "Capture One 12" to tell thisCollRef to set collSearchSuccess to ({} = (every collection whose name is nbrTargetCollName))
         repeat until collSearchSuccess
            tell application "Capture One 12" to tell thisCollRef to set refFoundSaColl to (every collection whose name is nbrTargetCollName and kind is smart album)
            if 0 < (count of refFoundSaColl) then
               tell application "Capture One 12" to set foundSaRules to (get (rules of (item 1 of refFoundSaColl)) as text)
               if foundSaRules contains ((item 1 of saRuleCriteriaL) & nbrTargetCollName & (item 2 of saRuleCriteriaL)) then
                  set {refFoundColl, collSearchMatch} to {(item 1 of refFoundSaColl), true}
                  exit repeat -- found a matching collection
               end if
            end if
            if (MaxCount < (ctrCollName + 1)) or not enableCounting then
               set {collSearchFail, collSearchSuccess} to {true, false}
               exit repeat
            end if
            set ctrCollName to ctrCollName + 1
            set nbrTargetCollName to nameTargetColl & separatorChar & ctrCollName
            tell application "Capture One 12" to tell thisCollRef to set collSearchSuccess to ({} = (every collection whose name is nbrTargetCollName))
         end repeat
         if collSearchFail then exit repeat
         
         if enableCreate and collSearchSuccess then
            if debugLogEnable then my loqThis(2, false, "Attempting to Create the collection \"" & nbrTargetCollName & "\"")
            set savRule to (item 1 of saCreateRuleL) & nbrTargetCollName & (item 2 of saCreateRuleL)
            tell application "Capture One 12" to tell thisCollRef to set {refFoundColl, collCreated} to {make new collection with properties {kind:smart album, name:nbrTargetCollName, rules:savRule}, true}
            if debugLogEnable and (0 < ctrCollName) then set debugText to "smart album \"" & nbrTargetCollName & "\"  in " & thisCollKind_S & " \"" & thisCollName & "\""
         end if
      end if
      
      if collCreated or collSearchMatch then
         set {hasFoundColl, refParentColl} to {true, thisCollRef}
         if debugLogEnable and collCreated then my loqThis(2, false, "Created " & debugText)
         if debugLogEnable and collSearchMatch then my loqThis(2, false, "Found " & debugText)
         exit repeat
      end if
   end repeat
   
   if (not hasFoundColl) and enableFindParents and (not collSearchFail) then set {refFoundColl, hasFoundColl, refParentColl} to my findSvaCollection((my findParentColl(thisCollRef)), nameTargetColl, enableCreate, false, enableCounting, InitCount, MaxCount, separatorChar, kindParentColl_L, saCreateRuleL, saRuleCriteriaL)
   
   if not hasFoundColl then my loqThis(0, false, "Could not find the smart album \"" & nbrTargetCollName & "\"")
   return {refFoundColl, hasFoundColl, refParentColl}
   
end findSvaCollection

on findParentColl(thisCollRef)
   ## Copyright 2019 Eric Valk, Ottawa, Canada   Creative Commons License CC BY-SA    No Warranty.
   ## General purpose handler to find the parent of a collection
   ## March 23 2019
   global debugLogEnable
   local errorText, errorText1, parentStringList, refPtr, docPtr, docName, parentPtr, parentRefList, startPtr, stopPtr
   if debugLogEnable then my loqThis(2, false, "Starting Parent Search")
   try
      get || of {thisCollRef}
   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("Didn'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 12" 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("Unable to find starting collection")
   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 12" 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 getHelp()
   return "In this script's Dialog: " & return & ¬
      "\"xxxx\" adds the selected variants to the smart album \"SVA_xxxx\"" & return & ¬
      "Shortcuts are: \"+\",\"-\",\"<\",\">\"" & return & ¬
      "\">\" adds clones of selected variants" & return & ¬
      "\"<\" adds clones of selected variants with adjustments reset" & return & ¬
      "\"+\" sets the SVA smart album using the title of the current collection:" & return & ¬
      "If the current collection is not an SVA, then the SVA will be \"SVA_thisCollectionName\"" & return & ¬
      "If the current collection is an SVA with no number then the SVA will be  \"SVA_thisCollectionName_1\"" & return & ¬
      "If the current collection is an SVA with a number then the number of the new SVA will be incremented" & return & return & ¬
      "\"#nnn\" adds the variants to an SVA ending with \"_nnn\"" & return & ¬
      "\"+*censored word*\"adds the variants to  an SVA ending with \"*censored word*\"" & return & ¬
      "--- n are numbers, x are letters" & return & return & ¬
      return & ¬
      "\"-\" removes selected variants instead of adding" & return & ¬
      "The short cut \"->\" selects the removed variants in the parent project." & return
end getHelp
Note that in this code, there is a handler findParentColl() which obtains the parent collections of a collection - this information is not directly available in any Capture One Applescript command.
Last edited by Eric Nepean on Mon Jun 24, 2019 4:46 am, edited 5 times in total.
Eric Nepean
 
Posts: 615
Joined: Sat Oct 25, 2014 8:02 am
Location: Ottawa

Re: Script for managing Stacks

Postby Eric Nepean » Sun Jun 23, 2019 9:23 pm

Part 2.

Copy and paste this code at the end of the code of part 1.
Code: Select all
##########################
## Capture One General Handlers  Version 2019/03/11

on validateCOP5(minCOPversionstr, maxCOPversionstr)
   ## Copyright 2019 Eric Valk, Ottawa, Canada   Creative Commons License CC BY-SA    No Warranty.
   ## General purpose initialisation handler for scripts using Capture One Pro
   ## Extract and check basic information about the Capture One application
   
   global debugLogEnable
   local COPProcList, theAppRef, numCOPversion, minCOPversion, maxCOPversion
   local digit_mult, Version_digit, min_digit, max_digit, copVersionStr
   local theAppName, copVersion
   
   tell application "System Events"
      if debugLogEnable then
         my loqThis(2, false, ("COP Processes:" & (get my joinlisttostring((get name of every process whose name begins with "Capture One" and background only is false), ", "))))
         my loqThis(3, false, ("All Processes: " & (get my joinlisttostring((get name of every process whose background only is false), ", "))))
      end if
      set COPProcList to every process whose name begins with "Capture One" and background only is false
      if (count of COPProcList) = 0 then return {hasErrors:true, errorText:(get my loqqed_Error_Halt5("COP is not running"))}
      if (count of COPProcList) ≠ 1 then return {hasErrors:true, errorText:(get my loqqed_Error_Halt5("Unexpected: >1 COP instances"))}
      set theAppRef to item 1 of COPProcList
      set theAppName to ((get name of theAppRef) as text)
      set copDetailedVersion to get version of my application theAppName
   end tell
   
   tell application "Capture One 12" to set copVersionStr to (get app version)
   set copVersion to (word -1 of copVersionStr)
   
   if debugLogEnable then
      my loqThis(2, false, ("theAppName: " & theAppName))
      my loqThis(1, false, copVersionStr)
      my loqThis(2, false, ("Capture One full Version " & copDetailedVersion))
   end if
   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 Capture One " & copDetailedVersion & " - supported versions are from " & minCOPversionstr & " to " & maxCOPversionstr)))}
   if not maxVersionPass then my loqThis(0, true, ("Caution: Capture One " & copDetailedVersion & " has not been verified yet"))
   return {hasErrors:false, theAppName:theAppName, copVersion:copVersion}
end validateCOP5

on validateCOPdoc5(theDocRef, validDocKindList)
   ## Copyright 2019 Eric Valk, Ottawa, Canada   Creative Commons License CC BY-SA    No Warranty.
   ## 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 12" to set theDocRef to get current document
   
   try
      tell application "Capture One 12" 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)
   ## Copyright 2019 Eric Valk, Ottawa, Canada   Creative Commons License CC BY-SA    No Warranty.
   ## 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 namesTopCollections, kindsTopCollections_s, countTopCollections, selectedCollectionRef, selectedCollectionIndex, kindSelectedCollection_s, nameSelectedCollection
   
   tell application "Capture One 12" to set {COPDocName, COPDocKind_p} to get {name, kind} of theDocRef
   set COPDocKind_s to convertKindList(COPDocKind_p)
   
   tell application "Capture One 12" 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 last collection
         end try
         set selectedCollectionRef to get current collection
      end if
      tell selectedCollectionRef to set {nameSelectedCollection, kindSelectedCollection_s} to {name, my convertKindList(kind)}
      set {namesTopCollections, kindsTopCollections_s} to {every collection's name, my convertKindList(every collection's kind)}
   end tell
   set countTopCollections to count of namesTopCollections
   
   repeat with collectionCounter from 1 to countTopCollections
      if (nameSelectedCollection = item collectionCounter of namesTopCollections) and ¬
         (kindSelectedCollection_s = item collectionCounter of kindsTopCollections_s) then
         set selectedCollectionIndex to collectionCounter
         exit repeat
      end if
   end repeat
   
   local selectedCollectionMirroredAtTopLast, bottomUserCollectionIndex, topUserCollectionIndex, countFavoriteCollections, namesFavoriteCollections
   
   if COPDocKind_s = "catalog" then
      repeat with collectionCounter from countTopCollections to 1 by -1
         if ("in Catalog" = item collectionCounter of namesTopCollections) and ¬
            ("smart album" = item collectionCounter of kindsTopCollections_s) then
            set topUserCollectionIndex to collectionCounter - 1
            exit repeat
         end if
      end repeat
      repeat with collectionCounter from 1 to countTopCollections
         if ("Trash" = item collectionCounter of namesTopCollections) and ¬
            ("smart album" = item collectionCounter of kindsTopCollections_s) then
            set bottomUserCollectionIndex to collectionCounter + 1
            exit repeat
         end if
      end repeat
      
      set selectedCollectionMirroredAtTopLast to ¬
         (selectedCollectionIndex = countTopCollections) and ¬
         ({"catalog folder", "favorite"} does not contain last item of kindsTopCollections_s)
      
      set {countFavoriteCollections, namesFavoriteCollections} to {missing value, missing value}
      
   else if COPDocKind_s = "session" then
      repeat with collectionCounter from countTopCollections to 1 by -1
         if ("favorite" ≠ item collectionCounter of kindsTopCollections_s) then
            set topUserCollectionIndex to collectionCounter
            exit repeat
         end if
      end repeat
      repeat with collectionCounter from 1 to countTopCollections
         if ("Trash" = item collectionCounter of namesTopCollections) and ¬
            ("favorite" = item collectionCounter of kindsTopCollections_s) then
            set bottomUserCollectionIndex to collectionCounter + 1
            exit repeat
         end if
      end repeat
      
      set countFavoriteCollections to countTopCollections - topUserCollectionIndex
      if 0 = 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
   set selectedCollectionIsUser to ¬
      (selectedCollectionMirroredAtTopLast or ¬
         ((selectedCollectionIndex ≥ bottomUserCollectionIndex) and (selectedCollectionIndex ≤ topUserCollectionIndex)))
   
   if topUserCollectionIndex < bottomUserCollectionIndex 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 convertKindList(kind_list)
   ## Copyright 2019 Eric Valk, Ottawa, Canada   Creative Commons License CC BY-SA    No Warranty.
   ## 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_s_list, input_is_list, theItem, kind_s1, fail_flag, code_start, kind_list, Kind_s_item, kind_code, kind_type
   
   set kind_s_list to {}
   set input_is_list to ("list" = (get (class of kind_list) as text))
   if input_is_list then
      if ("text" = (get (class of item 1 of kind_list) as text)) and ¬
         ("«" ≠ (get text 1 of item 1 of kind_list)) then return kind_list -- quick pass through if first item is OK
      repeat with theItem in kind_list
         tell application "Capture One 12" to set the end of kind_s_list to (get theItem as text)
      end repeat
      if ("«" ≠ (get text 1 of item 1 of kind_s_list)) then return kind_s_list
   else
      if ("text" = (get (class of kind_list) as text)) and ¬
         ("«" ≠ (get text 1 of kind_list)) then return kind_list -- quick pass through if first item is OK
      tell application "Capture One 12" to set kind_s1 to (get kind_list as text)
      if "«" ≠ (get text 1 of kind_s1) then return kind_s1 -- quick pass through if input is OK
      set kind_s_list to {kind_s1}
   end if
   
   set fail_flag to false
   set code_start to -5
   
   set kind_list to {}
   repeat with Kind_s_item in kind_s_list
      if ("«" ≠ (get text 1 of Kind_s_item)) or (16 > (count of Kind_s_item)) then ¬
         error (get my loqqed_Error_Halt5("convertKindList received an unexpected Kind string: " & Kind_s_item))
      
      set kind_code to get (text code_start thru (code_start + 3) of Kind_s_item)
      set kind_type to get (text code_start thru (code_start + 1) of Kind_s_item)
      
      if kind_type = "CC" then ## Collection Kinds
         if kind_code = "CCpj" then
            set the end of kind_list to "project"
         else if kind_code = "CCgp" then
            set the end of kind_list to "group"
         else if kind_code = "CCal" then
            set the end of kind_list to "album"
         else if kind_code = "CCsm" then
            set the end of kind_list to "smart album"
         else if kind_code = "CCfv" then
            set the end of kind_list to "favorite"
         else if kind_code = "CCff" then
            set the end of kind_list to "catalog folder"
         else
            set fail_flag to true
         end if
         
      else if kind_type = "CL" then ## Layer Kinds
         if kind_code = "CLbg" then
            set the end of kind_list to "background"
         else if kind_code = "CLnm" then
            set the end of kind_list to "adjustment"
         else if kind_code = "CLcl" then
            set the end of kind_list to "clone"
         else if kind_code = "CLhl" then
            set the end of kind_list to "heal"
         else
            set fail_flag to true
         end if
         
      else if kind_type = "CR" then ## Watermark Kinds
         if kind_code = "CRWn" then
            set the end of kind_list to "none"
         else if kind_code = "CRWt" then
            set the end of kind_list to "textual"
         else if kind_code = "CRWi" then
            set the end of kind_list to "imagery"
         else
            set fail_flag to true
         end if
         
      else if kind_type = "CO" then ## Document Kinds
         if kind_code = "COct" then
            set the end of kind_list to "catalog"
         else if kind_code = "COsd" then
            set the end of kind_list to "session"
         else
            set fail_flag to true
         end if
      else
         set fail_flag to true
      end if
      
      if fail_flag then ¬
         error (get my loqqed_Error_Halt5("convertKindList received an unexpected Kind string: " & Kind_s_item))
      
   end repeat
   
   if input_is_list then
      return kind_list
   else
      return item 1 of kind_list
   end if
   
end convertKindList


## my joinListToString

## Uncomment these next lines if to use these handlers as a Script Library  file  (".scptd")

(*
use AppleScript version "2.5"
use scripting additions
use U : script "Utilities_1215"

if false then false
*)

#########################
## Logging Handlers  Version 2019/02/23

on setupLoqqing5()
   ## Copyright 2019 Eric Valk, Ottawa, Canada   Creative Commons License CC BY-SA    No Warranty.
   ## Handler to setup the variables used by the loqqing system at the start of the script
   
   global debugLogEnable, Loqqing, loqResultDocRef, loqResultMethod, loqDialogTextList
   ## loqResultDocRef is kept separate from Loqqing to allow easy listing of Loqqing
   
   ## Default values are entered
   set {loqResultDocRef, loqResultMethod} to {false, "not Initialized"}
   set Loqqing to {stateResultDoc:false, gateResultsFile:false, enableResultsFile:false, initResultDoc:false, ResultsFileMaxDebug:0, debugLogLevel:0}
   set Loqqing to Loqqing & {stateResultsByClipboard:true, gateResultsByClipboard:true, enableResultsByClipboard:true}
   set Loqqing to Loqqing & {stateResultsByDialog:false, gateResultsDialog:false, enableResultsByDialog:false, maxDialogPercent:50, maxDialogLines:25, maxDialogChar:1000}
   set Loqqing to Loqqing & {stateResultsByNotification:false, gateResultsNotification:false, enableResultsByNotifications:false, enableNotifications:true, notificationsMaxDebug:0}
   set Loqqing to Loqqing & {stateResultsByLoq:true, gateParentLoqqing:true, enableResultsByLoq:true, initLoqqing:false, gateLoqqing:true}
   
end setupLoqqing5

on InitializeLoqqing5(DocName_Ext, sourceTitle)
   ## Copyright 2019 Eric Valk, Ottawa, Canada   Creative Commons License CC BY-SA    No Warranty.
   ## 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 parent_name, Script_Title, Loqqing, gateScriptProgress, loqDialogTextList, loqResultDocRef
   
   local LogMethods, LogHeader, date_string
   tell current application to set date_string to (current date) as text
   set LogMethods to {}
   set LogHeader to (sourceTitle & " results on " & date_string)
   
   local targetFileWasCreated, TextEditlist, createdLine
   if Loqqing's gateResultsFile and Loqqing's enableResultsFile then
      set end of LogMethods to DocName_Ext
      if not Loqqing's initResultDoc then
         set targetFileWasCreated to false
         set Loqqing's stateResultDoc to false
         set Loqqing's initResultDoc to true
         
         ## If TextEdit is already open and has the document open then add the headerline
         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 & LogHeader & return
                  set Loqqing's stateResultDoc to true
               end tell
            end if
         end if
      end if
      
      local targetFolderParent_a, targetFolderParent_p, targetFolderName, targetFolder_a, targetFolder_p, ResultDocPath_a, ResultDocPath_p, newFolderRef, newFileRef
      if not Loqqing's stateResultDoc or (false = loqResultDocRef) then
         -- create the document and the folder if necessary
         -- 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
         set targetFolderParent_a to alias (get path to desktop folder as text)
         set targetFolderParent_p to get POSIX path of targetFolderParent_a
         set targetFolderName to "ScriptReports"
         set targetFolder_p to (targetFolderParent_p & targetFolderName)
         set ResultDocPath_p to targetFolder_p & "/" & DocName_Ext
         
         try
            set ResultDocPath_a to (get alias POSIX file ResultDocPath_p)
         on error
            try
               set targetFolder_a to (get alias POSIX file targetFolder_p) --x1
            on error
               tell application "Finder" to set newFolderRef to make new folder at targetFolderParent_a with properties {name:targetFolderName}
               set targetFolder_a to newFolderRef as alias
            end try
            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
         
         set createdLine to ("Created by " & Script_Title & " on " & date_string)
         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 createdLine & return & return
               else
                  if (0 = (count of paragraphs)) then set paragraph 1 to createdLine & return & return
               end if
            end tell
            if 2 ≤ Loqqing's debugLogLevel then tell me to log ResultDocPath_p & ": " & createdLine
         end tell
         
         set Loqqing's stateResultDoc 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
   end if
   if Loqqing's gateResultsFile and 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: " & Script_Title & return
   end if
   
   local screenWidthO, screenHeightO, screenWidth, screenHeight, fontSize_pts, charactersPerLine, dotsPer_Point, linesPerScreen, borderLines
   if Loqqing's gateResultsDialog and 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
      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 fo 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 borderLines to 5
      set Loqqing's maxDialogLines to (get ((Loqqing's maxDialogPercent) / 100 * (linesPerScreen - borderLines)) as integer)
      set Loqqing's maxDialogChar to (get ((Loqqing's maxDialogPercent) / 100 * (linesPerScreen - borderLines) * charactersPerLine) as integer)
      if 2 ≤ Loqqing's debugLogLevel then log {"maxDialogPercent", Loqqing's maxDialogPercent, "screenHeight", screenHeight, "linesPerScreen", linesPerScreen, "maxDialogLines", Loqqing's maxDialogLines, "maxDialogChar", Loqqing's maxDialogChar}
   end if
   if Loqqing's gateResultsDialog and Loqqing's stateResultsByDialog and not Loqqing's enableResultsByDialog then
      set Loqqing's stateResultsByDialog to false
      set loqDialogTextList to ""
   end if
   
   if Loqqing's gateResultsByClipboard and Loqqing's enableResultsByClipboard then
      set end of LogMethods to "Clipboard"
      if not Loqqing's stateResultsByClipboard then
         set the clipboard to LogHeader
         set Loqqing's stateResultsByClipboard to true
      end if
   end if
   if Loqqing's gateResultsByClipboard and Loqqing's stateResultsByClipboard and not Loqqing's enableResultsByClipboard then
      set the clipboard to {}
      set Loqqing's stateResultsByClipboard to false
   end if
   
   global gateScriptProgress
   if not Loqqing's initLoqqing then
      set Loqqing's initLoqqing to true
      if ("Script Editor" = parent_name) then
         set Loqqing's gateParentLoqqing to true
         set Loqqing's gateLoqqing to true
         set gateScriptProgress to true
         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
      else if ("Script Debugger" = parent_name) then
         set Loqqing's gateParentLoqqing to true
         set Loqqing's gateLoqqing to true
         set gateScriptProgress to true
         ## Avoids compiler errors when Script Debugger is not present
         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"
      else
         set Loqqing's gateParentLoqqing to false
         set Loqqing's gateLoqqing to false
         set gateScriptProgress to false
      end if
   end if
   
   if Loqqing's gateParentLoqqing and Loqqing's enableResultsByLoq then
      set end of LogMethods to " " & parent_name & " Log"
      if not Loqqing's stateResultsByLoq then
         set Loqqing's stateResultsByLoq to true
         log "Results Logging enabled for: " & Script_Title
      end if
   end if
   if Loqqing's gateParentLoqqing and Loqqing's stateResultsByLoq and not Loqqing's enableResultsByLoq then
      set Loqqing's stateResultsByLoq to false
      log "Results Logging disabled for: " & Script_Title
   end if
   
   if (Loqqing's gateResultsNotification and Loqqing's enableResultsByNotifications) or Loqqing's enableNotifications then
      set end of LogMethods to "Notifications"
      if not Loqqing's stateResultsByNotification then
         --display notification "Notifications enabled for: " & Script_Title
         set Loqqing's stateResultsByNotification to true
      end if
   else if Loqqing's stateResultsByNotification then
      display notification "Notifications disabled for: " & Script_Title
      set Loqqing's stateResultsByNotification to false
   end if
   
   set LogMethods_S to my joinlisttostring(LogMethods, ", ")
   if 2 ≤ Loqqing's debugLogLevel then loqThis(2, false, ("Result Reported by " & LogMethods_S))
   return LogMethods_S
end InitializeLoqqing5

on loqThis(thisLogDebugLevel, MakeFront, log_Text)
   ## Copyright 2019 Eric Valk, Ottawa, Canada   Creative Commons License CC BY-SA    No Warranty.
   ## 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 ebabled - {Script Editor Log, Text Editor Log, Display Dialog}
   
   global parent_name, Loqqing, loqDialogTextList, loqResultDocRef
   local log_Text_S
   
   if thisLogDebugLevel ≤ Loqqing's debugLogLevel then
      set log_Text_S to my joinlisttostring(log_Text, "; ")
      set triggeredDialog to false
      
      if (thisLogDebugLevel = 0) then -- Critical errors
         if Loqqing's gateLoqqing then log (log_Text_S)
         if Loqqing's enableResultsByClipboard then set the clipboard to ((get the clipboard) & return & (log_Text_S as text))
         if Loqqing's enableResultsFile then ¬
            tell application "TextEdit" to tell text of loqResultDocRef to ¬
               set paragraph (1 + (count paragraphs)) to (log_Text_S & return)
         if Loqqing's enableResultsByDialog then display alert log_Text_S giving up after 10
         (Loqqing's enableResultsByNotifications and (thisLogDebugLevel ≤ 0))
      else if (thisLogDebugLevel ≤ 0) then -- Expected Results
         if Loqqing's enableResultsByLoq then log (log_Text_S)
         if Loqqing's enableResultsByClipboard then set the clipboard to ((get the clipboard) & return & (log_Text_S as text))
         
         if Loqqing's enableResultsFile then ¬
            tell application "TextEdit" to tell text of loqResultDocRef to ¬
               set paragraph (1 + (count paragraphs)) to (log_Text_S & return)
         
         if Loqqing's enableResultsByDialog then -- process the dialog last
            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
               display dialog loqDialogTextList
               set triggeredDialog to true
               set loqDialogTextList to ""
            end if
         end if
         
      else -- debugging information
         if Loqqing's gateLoqqing then log (log_Text_S)
         if Loqqing's enableResultsFile and ((thisLogDebugLevel ≤ Loqqing's ResultsFileMaxDebug) or not Loqqing's gateLoqqing) then ¬
            tell application "TextEdit" to tell text of loqResultDocRef to ¬
               set paragraph (1 + (count paragraphs)) to ((log_Text_S as text) & return)
      end if
      
      if (Loqqing's enableNotifications and (thisLogDebugLevel ≤ Loqqing's notificationsMaxDebug)) or ¬
         (Loqqing's enableResultsByNotifications 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 log_Text_S)
            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 triggeredDialog) and (MakeFront or (0 = thisLogDebugLevel)) then --
         if Loqqing's gateLoqqing then tell application "System Events" to set frontmost of process parent_name to true
         if Loqqing's enableResultsFile then tell application "System Events" to set frontmost of process "TextEdit" to true
      end if
      
   else
      set log_Text_S to ""
   end if
   return log_Text_S
end loqThis

on loqqed_Error_Halt5(errorText)
   ## Copyright 2019 Eric Valk, Ottawa, Canada   Creative Commons License CC BY-SA    No Warranty.
   ## General purpose handler for logging during script termination
   global debugLogEnable, Script_Title
   tell current application to set date_string to (current date) as text
   return (get (loqThis(0, true, ("Script \"" & Script_Title & "\" is exitting at " & date_string))) & (loqThis(0, true, ("Exit Reason: " & errorText & return))))
end loqqed_Error_Halt5

#########################
## General Utility Handlers  Version 2019/01/12

on compareVersion(testVersion_S, minVersion_S, maxVersion_S)
   ## Copyright 2019 Eric Valk, Ottawa, Canada   Creative Commons License CC BY-SA    No Warranty.
   ## 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
   
   if (text ≠ (get class of testVersion_S)) or (text ≠ (get class of minVersion_S)) or (text ≠ (get class of maxVersion_S)) then error "Text inputs only"
   
   set testVersion_LS to (splitstringtolist(removeLeadingTrailingSpaces(testVersion_S), "."))
   set minVersion_LS to (splitstringtolist(removeLeadingTrailingSpaces(minVersion_S), "."))
   set maxVersion_LS to (splitstringtolist(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 to false
   set lowerBoundFail to false
   set upperBoundPass to false
   set upperBoundFail to 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 (get (item group_ctr of testVersion_LS) as integer)
      if hasMinGroup then set minGroupVersion to (get (item group_ctr of minVersion_LS) as integer)
      if hasMaxGroup then set maxGroupVersion to (get (item group_ctr of maxVersion_LS) as integer)
      
      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 splitstringtolist(theString, theDelim)
   ## Public Domain
   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)
   ## Public Domain
   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 removeLeadingTrailingSpaces(theListString)
   ## 40% faster than a version which trims the string 1 space at a time
   ## handles both string and list input correctly
   -- local theListString, input_is_list, cleanList, thecount, hasTriggered, indexLow, indexHigh
   
   set input_is_list to ("list" = (get (class of theListString) as text))
   if not input_is_list then set theListString to {theListString}
   
   set cleanList to {}
   repeat with theString in theListString
      if ("text" = (get (class of theString) as text)) then
         set thecount to (get count of theString)
         set hasTriggered to false
         repeat with indexLow from 1 to thecount
            if " " ≠ (get text indexLow of theString as text) then
               set hasTriggered to true
               exit repeat
            end if
         end repeat
         if not hasTriggered then
            set theString to ""
         else
            repeat with indexHigh from -1 to (-thecount) by -1
               if " " ≠ (get text indexHigh of theString as text) then exit repeat
            end repeat
            set theString to text indexLow thru indexHigh of theString
         end if
      end if
      
      set the end of cleanList to theString
   end repeat
   
   if input_is_list then return cleanList
   
   return (get contents of theString)
end removeLeadingTrailingSpaces

on removeLeadingTrailingSpaces2(theListString, trimLeading, trimTrailing)
   ## 40% faster than a version which trims the string 1 space at a time
   ## handles both string and list input correctly
   local theListString, input_is_list, cleanList, thecount, foundChar, isAllBlanks, indexLow, indexHigh
   
   set input_is_list to ("list" = (get (class of theListString) as text))
   if not input_is_list then set theListString to {theListString}
   
   set cleanList to {}
   repeat with theString in theListString
      if ("text" = (get (class of theString) as text)) then
         set {thecount, indexLow, indexHigh, isAllBlanks} to {(get count of theString), 1, -1, false}
         if trimLeading then
            set foundChar to false
            repeat with indexLow from 1 to thecount
               if " " ≠ (get text indexLow of theString as text) then
                  set foundChar to true
                  exit repeat
               end if
            end repeat
            set isAllBlanks to not foundChar
         end if
         if trimTrailing and not isAllBlanks then
            set foundChar to false
            repeat with indexHigh from -1 to (-thecount) by -1
               if " " ≠ (get text indexHigh of theString as text) then
                  set foundChar to true
                  exit repeat
               end if
            end repeat
            set isAllBlanks to not foundChar
         end if
         if isAllBlanks then
            set theString to ""
         else
            set theString to text indexLow thru indexHigh of theString
         end if
      end if
      
      set the end of cleanList to theString
   end repeat
   
   if input_is_list then return cleanList
   return (get contents of theString)
end removeLeadingTrailingSpaces2

on removeTextFromList(theList, theBadChar)
   ## theBadChar may be a string or character, whereupon that string will be removed
   ## 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 string or character, whereupon that string will be removed
   ## 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
[/code]
Eric Nepean
 
Posts: 615
Joined: Sat Oct 25, 2014 8:02 am
Location: Ottawa


Return to Scripting



Who is online

Users browsing this forum: No registered users and 1 guest