DOpus.Output ("\n - - - - - - - ->") // ********** THE onInit FUNCTION ********************************************* function OnInit (initData) { initData.name = "SortBySubstringsOfFilestem" initData.desc = "Sort by substrings of the filestem." initData.copyright = "(c) Julianon 2017" initData.version = "1.0" initData.default_enable = true // ********** The dialogue to setup the sorting ******************************* var cmd = initData.AddCommand () cmd.name = "SetupSortConfigs" cmd.method = "onSetupSortConfigs" cmd.desc = initData.desc cmd.label = "Addin to test functions." // ********** Add the column needed for the sorting *************************** var cmd = initData.AddColumn() cmd.name = "Substrings" cmd.method = "onSubstrings" cmd.label = "Substrings" cmd.defwidth = "1px" // Make it invisible — cycle width 0px, 80px, 300px cmd.autogroup = true cmd.autorefresh = true cmd.justify = "left" } // End OnInit // ********** The dialogue to setup the sorting ******************************* function onSetupSortConfigs (scriptCommandData) { // ********** STEP 1: Read the persistent global variable varMCSC into vMCSC ** var oFunc = scriptCommandData.func // Some shortcuts var oCmd = oFunc.command var oSource = oFunc.sourcetab var oPath = oSource.path var sPath = String (oPath) var oVars = DOpus.vars //Global variables var oCr = DOpus.Create DOpus.Output ("Sort parameters are: nPosn, bLeft, nLength, bToEnd, bReverse") if (oVars.Exists ("varMCSC")) { // MCSC = "My Column Sort configuration" var vMCSC = oVars.Get ("varMCSC") } else { var vMCSC = oCr.vector vMCSC (0) = 0 } var nConfig = 1 var nSubstring = 1 var nParam = 0 for (nConfig = 1; nConfig < vMCSC.length; nConfig++) { DOpus.Output (" - - - - - - - -") DOpus.Output ("Config " + nConfig + " is from " + vMCSC (nConfig) (0)) for (nSubstring = 1; nSubstring < vMCSC (nConfig).length; nSubstring++) { var sOutput = "" for (nParam = 0; nParam < 5; nParam++) sOutput = sOutput + vMCSC (nConfig) (nSubstring) (nParam) + " " DOpus.Output ("Sort " + nSubstring + " parameters are: " + sOutput) } } // ********** STEP 2: Begin the while loop ************************************ var nChoice = -1 var bDlg = true var bChose2 = false var bChose3 = false while (!!nChoice) { // var bSubstringsCol = false // Does the column exist, and what is its width var nSubstringsWidth = 1 var eColumns = new Enumerator (oSource.format.columns) eColumns.moveFirst () while (!eColumns.atEnd ()) { var oColumn = eColumns.item () if (oColumn.name == "scp:SortBySubstringsOfFilestem/Substrings") { // Identify 'Substrings' column bSubstringsCol = true nSubstringsWidth = oColumn.width } eColumns.moveNext () } var oDlg = DOpus.dlg // The grand dialogue. It cycles until it is dismissed. if (bChose2 || bChose3) { if (bChose2) { oDlg.title = "Choose active config" var sMge = "Choose the active sort configuration. The current sort configurations are:\n" } if (bChose3) { oDlg.title = "Choose config to delete" var sMge = "Choose the configuration to delete. The current sort configurations are:\n" } } else { oDlg.title = "Choose action" var sMge = "Choose the action. The current sort configurations are:" } // oDlg.message is desperately compicated by all the cases for (nConfig = 1; nConfig < vMCSC.length; nConfig++) { sMge = sMge + "\n\nCONFIG " + doNumberToChar (nConfig) + ": Created in " sMge = sMge + vMCSC (nConfig) (0) for (nSubstring = 1; nSubstring < vMCSC (nConfig).length; nSubstring++) { // Name them var nPosn = vMCSC (nConfig) (nSubstring) (0) var bLeft = vMCSC (nConfig) (nSubstring) (1) var nLength = vMCSC (nConfig) (nSubstring) (2) var bToEnd = vMCSC (nConfig) (nSubstring) (3) var bReverse = vMCSC (nConfig) (nSubstring) (4) sMge = sMge + "\n Sort " + nSubstring + ": " sMge = sMge + "Skip " + nPosn + " from the " sMge = sMge + doCases (bLeft, "right", "left") + ". Then sort on " sToEnd = "the rest of the filestem" sMge = sMge + doCases (bToEnd, "the next " + nLength, sToEnd) sMge = sMge + doCases (bReverse, ".", ", reversed.") } } if (typeof vMCSC (0) == "number" && vMCSC (0) > 0) sMge = sMge + "\n\nACTIVE SORT CONFIGURATION: " + doNumberToChar (vMCSC (0)) else // If there is no active sort configuration, filesetm+ext is used sMge = sMge + "\n\n WARNING: There is no active sort configuration." oDlg.message = sMge if (bChose2 || bChose3) { // If we are choosing the active configuration or deleting var sBns = "|" for (nConfig = 1; nConfig < vMCSC.length; nConfig++) sBns = sBns + "&" + doNumberToChar (nConfig) + ".|" } else { // Original run-through of the dialogue var sBns = "|&New config|Change &Active config|&Delete configs|&Refresh lister|" if (bSubstringsCol) { // If the column already exists sBns = sBns + "Turn Substrings column &OFF|" } else { sBns = sBns + "Turn Substrings column &ON|" } if (bSubstringsCol) { // These are only displayed if the column is on sBns = sBns + "Sort &Forwards|Sort &Backwards|Cycle &Visibility|" } } oDlg.buttons = sBns + "&Cancel|" nChoice = oDlg.show // ********** STEP 3: The consequences of the dialogue choice ***************** if (bChose2) { // If the dialogue was to choose the active config vMCSC (0) = nChoice || vMCSC (0) nChoice = -1 bChose2 = false // Back to the initial conditions. if (!bSubstringsCol) oCmd.AddLine ("Set ColumnsAdd scp:SortBySubstringsOfFilestem/Substrings") oCmd.AddLine ("Set SortBy=scp:SortBySubstringsOfFilestem/Substrings") oCmd.AddLine ("Set SortReverse=Off") oCmd.Run () } if (bChose3) { // If the dialogue was to delete a config if (!!nChoice) { if (nChoice == vMCSC (0)) vMCSC (0) = 0 // This means no active configuration - issues a warning if (nChoice < vMCSC (0)) vMCSC (0) = vMCSC (0) - 1 // Step back 1 vMCSC.erase (nChoice) } nChoice = -1 bChose3 = false // Back to the initial conditions. } // nChoice = 1 is a long case below the other cases. It is STEP 4 if (nChoice == 2) // Change active config bChose2 = true if (nChoice == 3) // Delete a config bChose3 = true // if (nChoice == 4) Omit because the lister will be refreshed automatically if (nChoice == 5) { // Turn the column on or off if (bSubstringsCol) { oCmd.AddLine ("Set ColumnsRemove scp:SortBySubstringsOfFilestem/Substrings") } else { oCmd.AddLine ("Set ColumnsAdd scp:SortBySubstringsOfFilestem/Substrings") oCmd.AddLine ("Set SortBy=scp:SortBySubstringsOfFilestem/Substrings") oCmd.AddLine ("Set SortReverse=Off") } oCmd.Run () } if (bSubstringsCol) { // If the column is on: if (nChoice == 6) // Sort forwards oCmd.AddLine ("Set SortBy=scp:SortBySubstringsOfFilestem/Substrings") oCmd.AddLine ("Set SortReverse=Off") if (nChoice == 7) { // Sort backwards oCmd.AddLine ("Set SortBy=scp:SortBySubstringsOfFilestem/Substrings") oCmd.AddLine ("Set SortReverse=On") } if (nChoice == 8) { // Cycle column width 1px, 80px and 200px if (nSubstringsWidth < 5) { oCmd.AddLine ("Set ColumnsAdd scp:SortBySubstringsOfFilestem/Substrings(!,80)") } else { if (nSubstringsWidth < 85) oCmd.AddLine ("Set ColumnsAdd scp:SortBySubstringsOfFilestem/Substrings(!,300)") else oCmd.AddLine ("Set ColumnsAdd scp:SortBySubstringsOfFilestem/Substrings(!,1)") } } oCmd.Run () } // ********** STEP 4: Add a new sort configuration **************************** if (nChoice == 1) { // The last nChoice case --- Add a new config var nSubstring = 1 // A config can have multiple successive substrings var bContinue = true while (bContinue) { // Loops over successive substrings var bLeft = true var bReverse = false var bToEnd = false var nPosn = 0 var oDlgA = DOpus.dlg // First dialogue: Starting position of the substring oDlgA.title = "Sort" + nSubstring var sMgeA = "Specify the details of Sort " + nSubstring + ".\n\n" sMgeA = sMgeA + "How many characters before the substring begins?\n" sMgeA = sMgeA + " 0, 1, 2, 3, . . . to count from the left.\n" sMgeA = sMgeA + " -0, -1, -2, -3, . . . to count from the right." oDlgA.message = sMgeA oDlgA.max = 128 oDlgA.buttons = "&OK|&No more sorts|" var sInputA = "" while (bContinue && !sInputA) { // Input nPosn and nLength while (bContinue && !doCheckEachIsDigit (sInputA, true) ) { // Allow negative bContinue = !!oDlgA.show // This is the nPosn input sInputA = oDlgA.input } if (bContinue) { if (sInputA.charAt (0) == "-") { bLeft = false sInputA = sInputA.substr (1) } nPosn = parseInt (sInputA, 10) var oDlgB = DOpus.dlg // Second dialogue: Length of substring (or to the end) oDlgB.title = "Sort " + nSubstring if (nPosn == 0) { sMgeB = "The substring for Sort " + nSubstring + " begins at the " sMgeB = sMgeB + doCases (bLeft, "right", "left") + "-hand end.\n" } else { sMgeB = "Sort " + nSubstring + " will skip the " sMgeB = sMgeB + doCases (bLeft, "last ", "first ") sMgeB = sMgeB + doCases (nPosn, nPosn + " characters", "character") sMgeB = sMgeB + " from the " + doCases (bLeft, "right", "left")+ ".\n" } sMgeB = sMgeB + "How many characters to the " + doCases (bLeft, "left", "right") sMgeB = sMgeB + " are to form the substring?" sMgeB = sMgeB + "\n\nEnter a nonzero whole number:\n 1, 2, 3, 4, . . ." oDlgB.message = sMgeB oDlgB.max = 128 var sLabel = "Include &Everything to the " oDlgB.Options (0).label = sLabel + doCases (bLeft, "left.", "right.") oDlgB.Options (1).label = "Sort the &Reverse of this substring." oDlgB.Buttons = "&OK|Choose a &New starting position|&Cancel this sort string|" var sInputB = "" while (bContinue && !doCheckEachIsDigit (sInputB, false, false, true)) { var nChoiceB = oDlgB.show // This is the nLength input sInputB = oDlgB.input bToEnd = oDlgB.Options (0).state bReverse = oDlgB.Options (1).state bContinue = !!nChoiceB if (bToEnd) sInputB = "60" // An arbitrary positive number to escape the inner while if (nChoiceB == 2) { sInputA = "" // This will activate the middle while sInputB = "60" // An arbitrary positive number to escape the inner while } } // End inner while } // End if bContinue } // End middle while if (bContinue) { nLength = parseInt (sInputB, 10) if (nSubstring == 1) { var nConfig = vMCSC.length vMCSC (nConfig) = oCr.vector vMCSC (nConfig) (0) = sPath vMCSC (0) = nConfig } vMCSC (nConfig) (nSubstring) = oCr.vector (nPosn, bLeft, nLength, bToEnd, bReverse) nSubstring++ } } // End while bContinue for (nConfig = 1; nConfig < vMCSC.length; nConfig++) { DOpus.Output (vMCSC (nConfig) (0)) for (nSubstring = 1; nSubstring < vMCSC (nConfig).length; nSubstring++) { var sOutput = "" for (nParam = 0; nParam < 5; nParam++) sOutput = sOutput + vMCSC (nConfig) (nSubstring) (nParam) + " " DOpus.Output ("Sort " + nSubstring + " parameters are: " + sOutput) } } if (!bSubstringsCol) { oCmd.AddLine ("Set ColumnsAdd scp:SortBySubstringsOfFilestem/Substrings") } oCmd.AddLine ("Set SortBy=scp:SortBySubstringsOfFilestem/Substrings") oCmd.AddLine ("Set SortReverse=Off") oCmd.Run () } // End if nChoice == 1 oCmd.RunCommand ("Go Refresh") oSource.update () } // End while oVars.Set ("varMCSC", vMCSC) // Write the new vMCSC vector back to disk. oVars ("varMCSC").persist = true } // End onTestSnippetE // ********** Add the column needed for the sorting *************************** function onSubstrings (scriptColData) { // ********** STEP 1: Read the persistent global variable varMCSC into vMCSC ** var sStem = scriptColData.item.name_stem var nStem = sStem.length var sExt = scriptColData.item.ext var oVars = DOpus.vars if (!oVars.Exists ("varMCSC")) { // MCSC = "My Column Sort configuration" scriptColData.value = sStem + sExt // The default is the filename } else { var vMCSC = oVars.Get ("varMCSC") var nConfig = vMCSC (0) // This selects the sort configuration (none if zero) if (nConfig < 1 || nConfig >= vMCSC.length) { scriptColData.value = sStem + sExt // The default is the filename } else { var nSubstring = 1 var sEntry = "" var nMaxStem = -1 for (nSubstring = 1; nSubstring < vMCSC (nConfig).length; nSubstring++) { // Name them var nPosn = vMCSC (nConfig) (nSubstring) (0) var bLeft = vMCSC (nConfig) (nSubstring) (1) var nLength = vMCSC (nConfig) (nSubstring) (2) var bToEnd = vMCSC (nConfig) (nSubstring) (3) var bReverse = vMCSC (nConfig) (nSubstring) (4) // ********** STEP 2: Create the substrings to sort on ************************ if (bToEnd && nMaxStem == -1) { var oEnum = new Enumerator (scriptColData.tab.all) oEnum.moveFirst () while (!oEnum.atEnd ()) { nMaxStem = Math.max (nMaxStem, oEnum.item ().name_stem.length) oEnum.moveNext () } nLength = nMaxStem // Needed for buffering if bToEnd } // End if bToEnd if (bLeft) { // Create the substring if (bToEnd) sSubstring = sStem.substr (nPosn) else sSubstring = sStem.substr (nPosn, nLength) } else { if (bToEnd) sSubstring = sStem.substr (0, nStem - nPosn) else sSubstring = sStem.substr (nStem - nPosn - nLength, nLength) } if (bReverse) // Reverse it if required sSubstring = doReverseString (sSubstring) sSubstring = doBufferString (sSubstring, nLength) + "|" // Add | for clarity sEntry = sEntry + sSubstring // var sOutput = "" // Turn these lines on if a problem arises // for (nParam = 0; nParam < 5; nParam++) // sOutput = sOutput + vMCSC (nConfig) (nSubstring) (nParam) + " " // DOpus.Output (sOutput + " --> sSubstring = " + sSubstring) } // End For loop scriptColData.value = sEntry + sStem + sExt // Add filestem+ext at the end } // End if (nConfig < 1 || nConfig >= vMCSC.length) } // End if (!oVars.Exists ("varMCSC")) } // End onSubstrings (scriptColData) // **************************************************************************** // ********** HELPER FUNCTIONS ************************************************ // **************************************************************************** // ********** Division with a chosen least remainder ************************** function doDivision (nDividend, nDivisor, nLeastRemainder) { // Requires "new" // Intended only for integers. If TheDivisor = 0, then this.q and this.r are NaN // No return, so usage is: var oDivisor = new doDivision (nDividend, 5, -4) if (arguments.length < 3) var nLeastRemainder = 0 // Default remainders are 0, 1, 2, . . . , (nDivisor-1) if (arguments.length < 2) var nDivisor = 2 // Default divisor = 2, for odd and even this.n = nDividend // TheDividend this.d = nDivisor // TheDivisor this.r = (nDividend - nLeastRemainder) % nDivisor // Least remainder correction // Note: The % operator often returns a negative integer. if (this.r < 0) // Make the remainder positive or zero this.r = this.r + Math.abs (this.d) // Now the least remainder is zero this.r = this.r + nLeastRemainder // Now the least remainder is nLeastRemainder this.q = Math.round ((this.n - this.r) / this.d) // This is the quotient } // ********** Check character base has correct form, and output ASCII ******** function doCheckCharBase (sCharBase) { // A character base is a connected sequence of ASCII characters. // It is specified by a string consisting of its 1st & last characters, as "AZ". var sNewBase = "AZ" // The default if (arguments.length > 0) { if (typeof (sCharBase) == "string") { if (sCharBase != "") sNewBase = sCharBase.charAt (0) + sCharBase.charAt (sCharBase.length - 1) // Convert to a 2-character string by taking the first and last characters if (sNewBase.charCodeAt (1) < sNewBase.charCodeAt (0)) // Reverse if descending sNewBase = sNewBase.charAt (1) + sNewBase.charAt (0) } } return sNewBase } // ********** Base 10 number to character base number ************************* function doNumberToChar (nTenNumber, sCharBase, bASCII) { // nTenNumber is the whole number in decimal notation // sCharBase is a 2-char string in ascending ASCII order, or a positive number // bASCII = false will give the result as an array in ascending order if (arguments.length < 3) // Default is ASCII characters, otherwise return array bASCII = true if (arguments.length < 2) // Default is UC alphabet (only meaningful if bASCII) sCharBase = "AZ" sCharBase = doCheckCharBase (sCharBase) var nFirst = sCharBase.charCodeAt (0) - 1 // The least remainder below is 1 var nLast = sCharBase.charCodeAt (1) var nCharBase = nLast - nFirst // The greatest remainder is nCharBase var aChars = new Array // The remainders in ascending order var nIndex = 0 // The place index is also the array index var nPart = nTenNumber // The successive quotients while (nPart > 0) { // Division is completed when the quotient is 0 var oDivision = new doDivision (nPart, nCharBase, 1) // The least remainder of this division is 1, not the usual 0 aChars [nIndex] = oDivision.r nPart = oDivision.q nIndex ++ } if (bASCII) { var sChars = "" for (nIndex = 0; nIndex < aChars.length; nIndex++) sChars = String.fromCharCode (nFirst + aChars [nIndex]) + sChars return sChars // sChars is the string of characters in descending order } else { return aChars } } // ********** Reverse the characters of a string ****************************** function doReverseString (sString) { sString = String (sString) var sReverse = "" var nN = 0 for (nN = 0; nN < sString.length; nN++) sReverse = sString.charAt (nN) + sReverse return sReverse } // ********** Buffer a string to a given length ******************************* function doBufferString (sString, nBuffer, bLeftBuffer, bTruncate) { if (arguments.length < 4) bTruncate = false // Default: Don't truncate if (arguments.length < 3) bLeftBuffer = false // Default: Buffer on the right if (arguments.length < 2) nBuffer = 8 // Default: Buffer to length 8 (for old-style filenames) sString = String (sString) var nN = 0 if (bTruncate) sString = sString.substr (0, nBuffer) while (sString.length < nBuffer) { if (bLeftBuffer) sString = " " + sString else sString = sString + " " } return sString } // ********** Return one of any number of cases ******************************* // Do plurals by doCases (nApples, "apples", "apple"), with plural as default function doCases (nCase) { // nCase is the given case. Then the possible cases follow as: // Case 0, Case 1, Case 2, Case 3, ..., Case arguments.length-1 if (arguments.length < 2) { return "" // If no cases are given, or even no nCase, return "" } else { var nCases = arguments.length - 1 var nFall = 0 // The default is Case 0. It occurs if nCase is zero or false // or empty string or any other type, or if nCase + 1 is larger than nCases if (typeof (nCase) == "number") nFall = nCase if (typeof (nCase) == "boolean") // False is the default nFall = 1 * nCase // Convert a boolean variable to false = 0 or true = 1 if (typeof (nCase) == "string") { // Convert a string of digits to a number if (doCheckEachIsDigit (nCase)) nFall = parseInt (nCase, 10) // Default: negatives, empty and non-numbers } if (nFall < 0 || nFall + 1 > nCases) nFall = 0 return arguments [nFall + 1] // The default is Case 0 } } // ********** Check each character of a string is a digit ********************* function doCheckEachIsDigit (sString, bNegativeOK, bEmptyOK, bZeroOK) { if (arguments.length < 4) bZeroOK = true // Default is allow a string with no nonzero digit if (arguments.length < 3) bEmptyOK = false // Default is reject an empty string if (arguments.length < 2) bNegativeOK = false // Default is reject negatives (and initial + sign) var bCheck = false var bAllZeroes = true var sString = sString.toString () // Give it every change to be a string if (typeof sString == "string") { bCheck = !!(bEmptyOK || sString) // Empty string condition if (sString) { nChar = sString.charCodeAt (0) if ((nChar > 57 || nChar < 48) && nChar != 45 && nChar != 43) bCheck = false // Char 48 is 0 and char 57 is 9 if (!bNegativeOK && (nChar == 45 || nChar == 43)) bCheck = false // Char 45 is - and char 43 is + if (nChar > 48 && nChar < 58) // There is a nonzero digit bAllZeroes = false var nN = 0 for (nN = 1; nN < sString.length; nN++) { var nChar = sString.charCodeAt (nN) if (nChar < 48 || nChar > 57 ) // If it is not a digit bCheck = false if (nChar > 48 && nChar < 58) // There is a nonzero digit bAllZeroes = false } } // End if sString if (bAllZeroes && !bZeroOK) // Nonzero digit condition bCheck = false } // End if typeof return bCheck }