// NOTES ON THE ADAPTATIONS BY JULIANON OF LEO'S SCRIPT: // * Any previous button will work as before, // --- except only that system files are now excluded by default. // * By default, hidden files are included, and system files are excluded. // --- The switches SkipHidden/S and IncludeSystem/S reverse these defaults. // * Drive-hopping has been implemented, with two optional arguments: // --- ChooseDrive/S is a switch triggering a dialogue. // --- OmitDrives/K has syntax as in OmitDrives=D:,M:,Z: // * The Sibling argument is now of type /O. // --- Valid values are 1, -1, 2, -2, 3, -3, ..., Next (=1), Previous (=-1), Prev. // --- Sibling alone or with an invalid value triggers a dialogue. // --- if the argument is omitted, it defaults to Next. // * Depth/O has been added, describing the length of the up-and-down-chains. // --- Valid values are 1, 2, 3, 4, ... // --- Depth alone or with an invalid argument triggers a dialogue. // --- if the argument is omitted, it defaults to Depth=1 as previously. // * A new switch UserDefinedGo/S allows the use of the system involving // the keys F12, F11, F10, F9, that can make the new folder open in a new tab, // the other panel, or another single- or dual-panel lister. See // https://resource.dopus.com/viewtopic.php?f=18&t=26044 DOpus.Output (" - - - - - - - ->") // ********** THE onInit FUNCTION ********************************************* function OnInit (initData) { initData.name = "GoRelative" initData.desc = "Adds command to navigate to sibling folders." var sCopyright = "(C) 2015 Leo Davidson " initData.copyright = sCopyright + "(unauthorised adaptation by Julianon 2017)" initData.url = "https://resource.dopus.com/viewtopic.php?f=35&t=24496" initData.version = "1.1.1" // leo's version is Go_to_Relative.vbs V1.1 initData.default_enable = true var cmd = initData.AddCommand cmd.name = "GoRelative" cmd.method = "onGoRelative" cmd.desc = "Go to the next or previous sibling of the current folder." cmd.label = "GoRelative" var sTemplate = "Sibling/O,Wrap/S,SkipHidden/S,IncludeSystem/S,Depth/O," cmd.template = sTemplate + "ChooseDrive/S,OmitDrives/K,UserDefinedGo/S" } // End fn OnInit // ********** THE onGoRelative FUNCTION *************************************** function onGoRelative (scriptCommandData) { // STEP 1: Read the arguments ************************************************* var oArgs = scriptCommandData.func.args var oGotArg = oArgs.got_arg var oSource = scriptCommandData.func.sourcetab var oCommand = scriptCommandData.func.command var DFSU = DOpus.FSUtil var sError = "" var nN = 0 var bWrap = oArgs.Wrap // The default is not to Wrap var bSkipHidden = oArgs.SkipHidden // The default is include hidden files var bSkipSystem =!oArgs.IncludeSystem // The default is exclude system files var bChooseDrive = oArgs.ChooseDrive // Default next drive, with no dialogue sGo = "Go" // The default is the normal DOpus Go if (oGotArg.UserDefinedGo) sGo = "A@Go" // The switch changes to the A@Go of the F12--F9 system var aOmitDrives = new Array // The default is omit no ready drives if (oGotArg.OmitDrives) aOmitDrives = oArgs.OmitDrives.split (",") var nIncr = 1 // The default is the next sibling directory var bDialogue = false // only ask if oGotArg if (oGotArg.Sibling) { // Are we going to the next or to the previous sibling bDialogue = true sMode = oArgs.Sibling if (typeof (sMode) == "string") { var sMode = oArgs.Sibling.toLowerCase () if (sMode == "next" ) bDialogue = false if (sMode == "previous" || sMode == "prev") { bDialogue = false nIncr = -1 } if (doCheckEachIsDigit (sMode, true)) { // Negatives are OK nIncr = parseInt (sMode, 10) bDialogue = !nIncr // Zero triggers a dialogue, others do not } } } // Other modes may be added here in the future. if (bDialogue) { var oDlg = DOpus.dlg oDlg.title = "Next or previous" var sMessage = "Choose how many steps to take forward or backwards " sMessage = sMessage + "through the alphabetical list of siblings.\n\n" // sMessage = sMessage + "Enter a positive or negative whole number.\n" sMessage = sMessage + "Positive: Move forwards through the siblings.\n" sMessage = sMessage + "Negative: Move backwards through the siblings." oDlg.message = sMessage oDlg.max = 128 oDlg.defvalue = 1 oDlg.select = true oDlg.buttons = "|&OK|&Cancel|" var sInput = "" while (!doCheckEachIsDigit (sInput, true)) { // Negative is OK var nChoice = oDlg.show sInput = oDlg.input nIncr = parseInt (sInput, 10) if (!nIncr) sInput = "" if (!nChoice) { sError = "Operation cancelled." sInput = "0" } } // End while } // if bDialogue if (!sError) { var nDepth = 1 // The default is Depth 1 if (oGotArg.Depth) { nDepth = 0 sDepth = oArgs.Depth if (typeof sDepth == "string") { if (doCheckEachIsDigit (sDepth)) { nDepth = parseInt (sDepth, 10) } } if (nDepth == 0) { // Depth had no value or an invalid value var oDlg = DOpus.dlg oDlg.title = "Choose depth" sMessage = "Choose the depth of the subpath\n" sMessage = sMessage + "by entering a positive whole number." oDlg.message = sMessage oDlg.max = 128 oDlg.defvalue = 1 oDlg.select = true oDlg.buttons = "|&OK|&Cancel|" var sInput = "" while (!doCheckEachIsDigit (sInput) && !sError) { var nChoice = oDlg.show sInput = oDlg.input nDepth = parseInt (sInput, 10) if (!nDepth) sInput = "" if (!nChoice) sError = "Operation cancelled." } // End while } // End if nDepth } // End if oGotArg.Depth } // End if !sError // STEP 2: Sort out the source directory and its ascending chain ************** if (!sError) { var pHead = DFSU.Resolve (oSource.path) var aSiblings = new Array var sSibling = "" var nN = 0 var aParents = new Array while (nN < nDepth - 1) { // Dealing with nDepth > 1 if (!pHead.test_parent) { nN = nDepth - 1 // Stop here } else { aParents [nN] = pHead.filepart // Store succesive directories pHead.Parent nN++ } } // End while } // End if !sError // STEP 3A: Get the sibling directories if the ancestor is not a drive ******** if (!sError) { var pChild = DFSU.NewPath (pHead) // Clone pHead var sChild = String (pChild) var aHeadType = new Array ("directories", "directory") if (pHead.test_parent) { // If there is a parent pHead.Parent () // List the sibling folders, then sort. var eFolderEnum = DFSU.ReadDir (pHead, false) // no recursion if (eFolderEnum.error > 0) { sError = "Error " + eFolderEnum.error + " reading folder " sError = sError + pHead + "." } // Choosing the folders: // Test if we can access the folder, and skip it if not. // If error is non-zero but complete is not set, it means we // can read the dir, but may be unable to get information // about some of its children, which is fine. Error 18 is // ERROR_NO_MORE_FILES which just means the folder is empty. if (!sError) { var nEntry = 0 while (!eFolderEnum.complete) { var oItem = eFolderEnum.Next var sItem = String (oItem) if (oItem.is_dir) { if ((oItem.attr & 2) == 0 || !bSkipHidden || sItem == sChild) { if ((oItem.attr & 4) == 0 || !bSkipSystem || sItem == sChild) { var eFolderEnum2 = DFSU.ReadDir (oItem, false) // No recn var nFolderErr2 = eFolderEnum2.error if (nFolderErr2 == 0 || nFolderErr2 == 18 || !eFolderEnum2.complete) { aSiblings [nEntry] = oItem nEntry++ } } } } } // End while } // End of the inner if !sError // STEP 3B: Get the sibling drives if the ancestor is a drive ***************** } else { // That is, if !pHead.test_parent var aHeadType = new Array ("drives", "drive") var nDrive = pHead.drive var sMessage = "" if (nDrive == 0) { // No parent, not drive letter, so give up. sMessage = "The path cannot be resolved.\n\n" } else { if (bChooseDrive) sMessage = "The ancestor is a drive.\n\n" } if (!sError) { var oFSO = new ActiveXObject ("Scripting.FileSystemObject") var eDriveEnum = new Enumerator (oFSO.drives) var nEntry = 0 eDriveEnum.moveFirst () while (!eDriveEnum.atEnd ()) { var oDrive = eDriveEnum.item () var bOmit = false for (nN = 0; nN < aOmitDrives.length; nN++) { if (aOmitDrives [nN].charAt (0) == String (oDrive).charAt (0)) bOmit = true } if (oDrive.IsReady && !bOmit) { var oDrive = DFSU.NewPath (oDrive + "\\") var eDriveEnum2 = DFSU.ReadDir (oDrive, false) // No recn var nFolderErr2 = eDriveEnum2.error if (nFolderErr2 == 0 || nFolderErr2 == 18 || !eDriveEnum2.complete) { aSiblings [nEntry] = oDrive nEntry++ } } eDriveEnum.moveNext () } // End while var nSiblings = aSiblings.length if (sMessage) { // This is the dialogue to choose a drive manually. var oDlg = DOpus.dlg oDlg.title = "Choose drive" oDlg.message = sMessage + "Choose the new drive to use." var sButtons = "" for (nN = 0; nN < nSiblings; nN++) { var sButton = String (aSiblings [nN]).substr (0, 2) sButtons = sButtons + "|&" + sButton } oDlg.buttons = sButtons + "|&0. Cancel|" var nChoose = oDlg.show if (nChoose == 0) { sError = "Operation cancelled." } else { sSibling = aSiblings [nChoose - 1] } } // End if sMessage } // End if !sError } // End else (that is, drive-letter case) } // End if !sError // STEP 4: Sort the sibling list and locate the current sibling. ************** if (!sError && !sSibling) { aSiblings.sort (doSortAlpha) // The sort function is at the end. var nSiblings = aSiblings.length var nCurItem = -1 for (nN = 0; nN < nSiblings; nN++) { if (String (aSiblings [nN]) == sChild) { nCurItem = nN } } if (nCurItem == -1) { sError = "The selected item was not found amongst the " sError = sError + aHeadType [0] + "." } } // STEP 5: Find the next or previous sibling ********************************** if (!sError && !sSibling) { var nOrigItem = nCurItem nCurItem = nCurItem + nIncr // The guts of it if (nCurItem >= nSiblings || nCurItem < 0) { var oDivision = new doDivision (nCurItem, nSiblings) nCurItem = oDivision.r // This remainder will be >= 0 and < nSiblings if (!bWrap) sError = "There are no more " + aHeadType [0] + "." } if (nOrigItem == nCurItem) sError = "Could not find another " + aHeadType [1] + " to go to." if (!sError) sSibling = aSiblings [nCurItem] } // STEP 6: Issue the command ************************************************** if (!sError) { for (nN = aParents.length - 1; nN >=0; nN--) { if (DFSU.Exists (sSibling + "\\" + aParents [nN])) { sSibling = sSibling + "\\" + aParents [nN] } else { sError = "Opening an ancestor of the target." } } // End for var sCommand = sGo + ' Path="' + sSibling + '"' // The final command DOpus.Output (sCommand) oCommand.RunCommand (sCommand) // This is the actual command } // End if !sError if (sError != "Cancel" && sError != "") { DOpus.Output (sError) var oDlg = DOpus.dlg oDlg.title = "Error" oDlg.message = sError oDlg.buttons = "&OK" oDlg.show } // End if sError DOpus.Output (" <- - - - - - - -") } // End fn onGoRelative // **************************************************************************** // ********** Helper functions ************************************************ // **************************************************************************** // ********** Sort objects alphabetically by converting to string ************* function doSortAlpha (oObjA, oObjB){ // Sort function for siblings var sA = String (oObjA).toLowerCase () var sB = String (oObjB).toLowerCase () return (sA > sB) ? 1 : (sA == sB) ? 0 : -1 } // ********** Check if every character in a string is a digit ***************** function doCheckEachIsDigit (sString, bNegativeOK, bEmptyOK) { if (arguments.length < 3) bEmptyOK = false // Default is to reject an empty string if (arguments.length < 2) bNegativeOK = false // Default is to reject negatives var bCheck = false var sString = sString.toString () // Give it every change to be a string var nString = sString.length if (typeof sString == "string") { if (bEmptyOK || sString.length > 0) { // Empty string condition bCheck = true var nN = 0 for (nN = 0; nN < sString.length; nN++) { var nChar = sString.charCodeAt (nN) if (nChar < 48 || nChar > 57 ) { // Checked by DOS character codes if (nN > 0 || nChar != 45 || !bNegativeOK || nString == 1) bCheck = false } } } } return bCheck } // ********** 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 }