CycleListers (Cycle through Listers)

CycleListers allows you to cycle through all open listers, similar to switching between open documents in an office program. It serves as an alternative to the Windows functions accessed via Alt-Tab or Win-Tab, offering a faster way to navigate between them. The order in which the listers are activated is determined by their window handles (HWND). The listers will be cycled in an endless loop.

Three arguments are supported:

Argument Description
LASTACTIVE Activate the last active lister. This lets you quickly switch between two listers. Takes priority over the other arguments.
REVERSE Reverse the looping direction.
ONLYCURRENTDESKTOP Ignore listers on other virtual desktops.

CycleListers works everywhere. I recommend putting it on global hotkeys.

How to set up and use

:one: Save CommandCycleListers.js.txt to   ↓

%appdata%\GPSoftware\Directory Opus\Script AddIns

:two: Add the new command to a hotkey.

CycleListers
<?xml version="1.0"?>
<button backcol="none" display="label" hotkey="win+alt+f6" textcol="none">
	<label>CycleListers</label>
	<function type="normal">
		<instruction>CycleListers</instruction>
	</function>
</button>
CycleListers REVERSE
<?xml version="1.0"?>
<button backcol="none" display="label" hotkey="win+alt+f7" textcol="none">
	<label>CycleListers</label>
	<function type="normal">
		<instruction>CycleListers REVERSE</instruction>
	</function>
</button>
CycleListers LASTACTIVE
<?xml version="1.0"?>
<button backcol="none" display="label" hotkey="win+alt+f8" textcol="none">
	<label>CycleListers</label>
	<function type="normal">
		<instruction>CycleListers LASTACTIVE</instruction>
	</function>
</button>

:three: The script creates a global variable listerCount. It could be used to hide a button if only one lister is open:

@hideif:=Val("$glob:listerCount")<2
CycleListers LASTACTIVE

It also creates the global variable lastLister that contains the last lister's HWND. Outside of debugging scripts, its usefulness is probably rather limited.

Things you might enjoy reading

FAQ How to use buttons and scripts from this forum

The script's inner workings

JScript
function OnInit(initData) {
    initData.name = 'CycleListers';
    initData.version = '2024-07-05';
    initData.url = 'https://resource.dopus.com/t/cyclelisters-cycle-through-listers/51247';
    initData.desc = 'Cycle through Listers';
    initData.default_enable = true;
    initData.min_version = '12.0';
}

function OnAddCommands(addCmdData) {
    var cmd = addCmdData.AddCommand();
    cmd.name = 'CycleListers';
    cmd.method = 'OnCycleListers';
    cmd.desc = 'Cycle through Listers';
    cmd.label = 'CycleListers';
    cmd.template = '' +
        'lastactive/s,' +
        'reverse/s,' +
        'onlycurrentdesktop/s';
    cmd.hide = false;
    cmd.icon = 'script';
}

function OnCycleListers(scriptCmdData) {
    if (DOpus.listers.count < 2) return;

    var cmd = scriptCmdData.func.command;
    var args = scriptCmdData.func.args;
    var vec = DOpus.Create().Vector();
    var listerToActivate;

    cmd.deselect = false;

    if (args.lastactive &&
        DOpus.vars.Exists('lastLister') &&
        DOpus.vars.Get('lastLister') != String(DOpus.listers.lastactive) &&
        ListerExists(DOpus.vars.Get('lastLister'))) {
        listerToActivate = DOpus.vars.Get('lastLister');
    } else {
        for (var i = 0; i < DOpus.listers.count; i++) {
            var item = DOpus.listers(i);

            if (args.onlycurrentdesktop &&
                item.desktop != DOpus.listers.lastactive.desktop) continue;

            var listerID = Number(String(item)); // the lister's HWND
            vec.push_back(listerID);

            if (item.lastactive) var lastActiveListerID = listerID;
        }

        if (vec.count < 2) return;

        vec.sort();

        var k = 0;
        while (vec(k) != lastActiveListerID) k++;

        if (args.reverse) {
            k--;
            if (k < 0) k = vec.count - 1;
        } else {
            k++;
            if (k == vec.count) k = 0;
        }

        listerToActivate = vec(k);
    }

    cmd.SetSourceTab(DOpus.listers(listerToActivate).activetab);
    cmd.RunCommand('Set LISTERCMD=tofront');
}

function OnActivateLister(activateListerData) {
    DOpus.vars.Set('listerCount', DOpus.listers.count);  // reference as {$glob:listerCount} or Val("$glob:listerCount")
    if (!activateListerData.active) {
        DOpus.vars.Set('lastLister', String(activateListerData.lister));
    }
    var cmd = DOpus.Create().Command();
    cmd.UpdateToggle();
}

function ListerExists(listerID) {
    for (var i = 0; i < DOpus.listers.count; i++) {
        if (String(DOpus.listers(i)) == String(listerID)) return true;
    }
    return false;
}
7 Likes

@lxp Neat! I have added this command to a toolbar. Is there a way (evaluator?) of hiding the corresponding button if only one lister is open?

Yes.

:one: Add this to your script add-ins

function OnActivateLister(activateListerData) {
    DOpus.vars.Set('listerCount', DOpus.listers.count);
}

by saving EventSaveListerCountAsVar.js.txt to

%appdata%\GPSoftware\Directory Opus\Script AddIns

:two: Add this to your button

@hideif:=Val("$glob:listerCount")<2
1 Like

Love it!! Thanks for sharing!

You instructions need to specify which AppData folder it should go in: Local; LocalLow; or Roaming. I found it in Roaming..

If you paste the entire path (%app ... Ins) Windows will handle this.

Wow, learned something new!! Thanks..

A neat solution @lxp. There is a short delay before the CycleListers button appears on the second lister but I expect that's unavoidable given the way the method works by combining an event and global variable.

Update 2024-07-05

  • Added switch LASTACTIVE that activates the last active lister.
  • Integrated the variable listerCount as discussed above
1 Like

Please try today's new version and see if the button updates more quickly. EventSaveListerCountAsVar.js.txt should be disabled.

Nice update ! Thankyou.
I noticed a very small problem.
The lister count does not update after a Close Alllisters=collapse,unique command, which only matters at all if @hideif:=Val("$glob:listerCount")<2 is used.

To be clear, EventSaveListerCountAsVar.js.txt is no longer needed, correct ?
I don't have it installed.

Yes. To update the variable when listers close, append this to the script:

function OnCloseLister(closeListerData) {
    DOpus.vars.Set('listerCount', DOpus.listers.count);
    var cmd = DOpus.Create().Command();
    cmd.UpdateToggle();
}

Correct, I've added it to the script.

Sorry,
I added that code to the end of the script and made sure that was the installed version,
but it still isn't working here.

Yes, you are right. This looks like a timing problem. Adding

@toggle:update

to the buttons doesn't seem to make a difference (won't hurt either).

I'll see what I can find.