RemoveEmptyColumns

RemoveEmptyColumns removes all columns from the file display that are empty for all selected items.

Example 1


Example 2


How to set up and use

:one: Save

Opus v12: CommandRemoveEmptyColumns.js.txt

Opus v13: CommandRemoveEmptyColumns.js.txt

to   

%appdata%\GPSoftware\Directory Opus\Script AddIns

:two: Add the new command to a button, hotkey, context menu, etc. like any built-in command, or run it from the FAYT Command field.

Things you might enjoy reading

How to use buttons and scripts from this forum

The script's inner workings

JScript v12
function OnInit(initData) {
    initData.name = 'RemoveEmptyColumns';
    initData.version = '2023-10-05';
    initData.url = 'https://resource.dopus.com/t/removeemptycolumns/43929';
    initData.desc = 'RemoveEmptyColumns';
    initData.default_enable = true;
    initData.min_version = '12.0';
}

function OnAddCommands(addCmdData) {
    var cmd = addCmdData.AddCommand();
    cmd.name = 'RemoveEmptyColumns';
    cmd.method = 'OnRemoveEmptyColumns';
    cmd.icon = 'script';
}

function OnRemoveEmptyColumns(scriptCommandData) {
    var cmd = scriptCommandData.func.command;
    var tab = scriptCommandData.func.sourcetab;
    var fsu = DOpus.FSUtil();
    var stt = DOpus.Create().StringTools();

    cmd.deselect = false;

    if (tab.selected.count == 0) return;

    var map = DOpus.Create().Map();

    // Some column labels can be mapped to column names automatically...

    for (var e = new Enumerator(tab.format.columns); !e.atEnd(); e.moveNext()) {
        var col = e.item();
        map(col.label.toLowerCase()) = col.name;
    }

    // ... some need to be defined manually:
    map('#') = 'index';
    map('access date') = 'accesseddate';
    map('access time') = 'accessedtime';
    map('accessed') = 'accessed';
    map('attr') = 'attr';
    map('audio streams') = 'audiocount';
    map('bitrate') = 'mp3bitrate';
    map('bits') = 'picdepth';
    map('blake3 checksum') = 'blake3sum';
    map('channel') = 'channel';
    map('comments') = 'comments';
    map('crc32 checksum') = 'crc32sum';
    map('created') = 'created';
    map('creation date') = 'createddate';
    map('creation time') = 'createdtime';
    map('date') = 'modifieddate';
    map('description') = 'imagedesc';
    map('disc') = 'mp3disc';
    map('document created') = 'doccreateddate';
    map('encoder') = 'mp3encodingsoftware';
    map('ext') = 'ext';
    map('files (total)') = 'filecounttotal';
    map('files') = 'filecount';
    map('folders (total)') = 'dircounttotal';
    map('folders') = 'dircount';
    map('fourcc') = 'fourcc';
    map('hd') = 'ishd';
    map('hdr') = 'hdrtypes';
    map('instructions') = 'instructions';
    map('location') = 'pathrel';
    map('md5 checksum') = 'md5sum';
    map('modified') = 'modified';
    map('name') = 'name';
    map('relative created') = 'cdaterel';
    map('relative modified') = 'daterel';
    map('relative size on disk') = 'disksizerel';
    map('relative size') = 'sizerel';
    map('repeat') = 'isrepeat';
    map('res x') = 'picresx';
    map('res y') = 'picresy';
    map('samples') = 'mp3samplerate';
    map('sha-1 checksum') = 'shasum';
    map('sha-256 checksum') = 'sha256sum';
    map('sha-512 checksum') = 'sha512sum';
    map('size on disk') = 'disksize';
    // map('size on disk') = 'disksizeauto';   // ***
    // map('size on disk') = 'disksizekb';     // ***
    map('size') = 'sizeauto';
    map('station') = 'station';
    map('status') = 'status';
    map('subtitles') = 'subtitlecount';
    map('time') = 'modifiedtime';
    map('title') = 'mp3title';
    map('track') = 'mp3track';
    map('uncompressed') = 'uncompressedsize';
    map('video streams') = 'videocount';

    var tmpFile = fsu.GetTempFilePath();
    cmd.RunCommand('Print FOLDER=selected AS=tab FLATVIEW=no QUIET TO="' + tmpFile + '"');

    var fileDisplay = DOpus.Create().Vector();
    for (var e = new Enumerator(DOpus.Create().Vector(stt.Decode(fsu.GetItem(tmpFile).Open().Read(), 'utf8').split('\r\n'))); !e.atEnd(); e.moveNext()) {
        var item = e.item();
        if (item.length == 0) continue;
        fileDisplay.push_back(DOpus.Create().Vector(item.split('\t')));
    }

    if (fileDisplay.empty) {
        DOpus.Output('Could not print File Display!?');
        return;
    }

    var cmdLine = '';

    for (var k = 0; k < fileDisplay(0).length; k++) {
        var len = 0;
        for (var j = 1; j < fileDisplay.length; j++) {
            len += fileDisplay(j)(k).length;
        }
        if (len) continue;

        var label = fileDisplay(0)(k).toLowerCase();

        if (map.exists(label)) {
            if (cmdLine) cmdLine += ',';
            cmdLine += map(label);
        } else {
            DOpus.Output('Label not found: ' + label);
        }
    }

    if (!cmdLine) return;

    cmdLine = 'Set COLUMNSREMOVE="' + cmdLine + '"';

    DOpus.Output(cmdLine);
    cmd.RunCommand(cmdLine);
}
JScript v13
function OnInit(initData) {
    initData.name = 'RemoveEmptyColumns';
    initData.version = '2023-12-07';
    initData.url = 'https://resource.dopus.com/t/removeemptycolumns/43929';
    initData.desc = 'RemoveEmptyColumns';
    initData.default_enable = true;
    initData.min_version = '13.0.50';
}

function OnAddCommands(addCmdData) {
    var cmd = addCmdData.AddCommand();
    cmd.name = 'RemoveEmptyColumns';
    cmd.method = 'OnRemoveEmptyColumns';
    cmd.icon = 'script';
}

function OnRemoveEmptyColumns(scriptCommandData) {
    var cmd = scriptCommandData.func.command;
    var tab = scriptCommandData.func.sourcetab;

    if (tab.selected.count == 0) return;

    var fsu = DOpus.FSUtil();
    var ssi = DOpus.Create().StringSetI();
    var stt = DOpus.Create().StringTools();

    cmd.deselect = false;

    var fileDisplay = [];

    for (var e = new Enumerator(tab.selected); !e.atEnd(); e.moveNext()) {
        ssi.insert(e.item().realpath.pathpart);
    }

    for (var e = new Enumerator(ssi); !e.atEnd(); e.moveNext()) {
        var tmpFile = fsu.GetTempFilePath();

        var cmdLine = 'Print' +
            ' FOLDER=selected' +
            ' AS=tab' +
            ' KEYWORDS' +
            ' ENCODING=utf8' +
            ' QUIET' +
            ' TO="' + tmpFile + '"';

        cmd.SetSource(e.item());
        cmd.RunCommand(cmdLine);

        var tmp = stt.Decode(fsu.GetItem(tmpFile).Open().Read(), 'utf8').split('\r\n');

        for (var i = 0; i < tmp.length; i++) {
            if (i == 0 && fileDisplay.length > 0) continue;
            var line = tmp[i];
            if (line.length == 0) continue;
            fileDisplay.push(line.split('\t'));
        }
    }

    if (fileDisplay.length == 0) {
        DOpus.Output('Could not print File Display!?');
        return;
    }

    cmdLine = '';

    for (var k = 0; k < fileDisplay[0].length; k++) {
        var len = 0;
        for (var j = 1; j < fileDisplay.length; j++) {
            len += fileDisplay[j][k].length;
        }
        if (len) continue;

        if (cmdLine) cmdLine += ',';
        cmdLine += fileDisplay[0][k]; // keyword
    }

    if (!cmdLine) return;

    cmdLine = 'Set COLUMNSREMOVE="' + cmdLine + '"';

    cmd.RunCommand(cmdLine);
}
6 Likes

@lxp. Alexander, after using this script I realised how useful and handy it is to rationalise Lister space. I found that it saves effort in scrolling to a column that can be pushed off screen because some files have large file names or other column information. Thank you!

Users need to be aware that it hides the empty columns on the selected folder/file. This means that other files in the lister that have populated columns will also have those columns hidden. To get the column back you need to refresh DOPUS so that it returned to your saved configuration.

Is it possible to develop this as a toggle? I am not a programmer, but I imagine that the script would save (remember) the columns that are hidden, and then use this information to restore the columns then when the button is toggled. I think that this would be better that refreshing DOPUS because users are essentially working on an individual lister.

Columns in Opus cannot be hidden like columns in a spreadsheet. The script removes them from the File Display.

To get these columns back, save them as a Folder Format via...

Set SAVEFORMAT=favorite FORMAT=MyTempFolderFormat
RemoveEmptyColumns

... and restore them via

Set FORMAT=MyTempFolderFormat

Styles would work in this scenario as well:

Prefs STYLESAVE=MyTempStyle
RemoveEmptyColumns
Prefs STYLE=MyTempStyle

Thank you, that works well.

When a file or folder is selected within a Lister, this button toggles between:

  1. saving the empty columns and deleting the empty columns
    AND
  2. replacing the columns to return to original Lister layout.
<?xml version="1.0"?>
<button backcol="none" display="both" icon_size="large" textcol="none">
	<label>Tgle Col</label>
	<tip>Toggle empty columns off and on</tip>
	<icon1>#swapsourcedest_horiz</icon1>
	<function type="normal">
		<instruction>@toggle:if $glob:MyToggle</instruction>
		<instruction>@ifset:$glob:MyToggle</instruction>
		<instruction>Set FORMAT=MyTempFolderFormat</instruction>
		<instruction>@set glob:MyToggle</instruction>
		<instruction>@ifset:else</instruction>
		<instruction>Set SAVEFORMAT=favorite FORMAT=MyTempFolderFormat</instruction>
		<instruction>RemoveEmptyColumns</instruction>
		<instruction>@set glob:MyToggle=1</instruction>
	</function>
</button>

Toggle Blank Columns.dop (697 Bytes)

Update 2023-10-05:

  • Added support for columns that were introduced by Opus v13
3 Likes

Great work! I had issues whenever the column label is different than their header (Since you're using PRINT command, who use their headers, not their labels) Changing this line :
map(col.label.toLowerCase()) = col.name;
To:
map(col.header.toLowerCase()) = col.name;
solves it. Also no need for manual map definitions.
Sidenote: You may want to add ENCODING=utf8nobom to the PRINT command, since there are problems understanding some characters (in my case it did not recognize the 'ñ').

That's odd because the temp file actually contains the labels as column titles, not the headers. The only problem I have encountered is two columns using the same label. In that case, only one column gets removed. Can you give an example of columns that caused issues?

I guess I'll request an argument for Print that lets us pick name, label, and header as column title :slight_smile:

Definitely a good idea :+1:

Unless there's an option to change this and I'm not aware of, the PRINT command use the values for Column's headers, not labels (but I'm not as expert as you in DO so who knows)

Failures could occur in script columns where label and header are different. The fact that you declare entries in the map manually solves precisely the differences you found between header and label values in some built-in columns, but this is not effective for any language other than English.
The table below shows some examples, only for English (It can be even more depending on the language). You can notice that Header and what Print shows values are the same, always.

Name Label Header What Print shows
index Index # #
extdir Extension (dirs) Ext Ext
sizeauto Size (auto) Size Size
attr Attributes Attr Attr
parentlocation Parent folder (full) Parent Folder Parent Folder
accessed Accessed (date and time) Accessed Accessed

Sometimes I'm not good at picking up sarcasm, so I'll say go for it.

@lxp. Hi Alexander, I still find this script useful and have incorporated it into the button I have listed in this thread. However, I cannot get it to "hide" the "Description Column" when it is empty.

@Leo kindly described the differences between two possible "Description" columns here: What is the difference between "Description" and "User Description" Columns

Looking at your script I can see that you map column names. So I attempted to map the Description Column: "map('description') = 'swizard.description';" The latter name comes from the Columns dialogue box within DOPUS. This did not work. Am I doing something wrong?

I've never seen this before, where did you find it?


You can get this to work by out commenting

map('description') = 'imagedesc';

But it comes at a cost: You'll get a message "Label not found: description" when trying to remove imagedesc.

Can't think of a solution for both right now other than to finally follow up on the promise I made further up a few weeks ago and write that feature request :slight_smile:

Thanks for the quick response. I commented out, your map line, and deleted my mapping attempt, and it worked.

Looking at the image you can see several script entries. Since the Description column is configurable, I figured that the script name was the real name of the column.

Ah okay, a script column, I see :slight_smile:

These should always work, and if they don't the script will spit out a message indicating the missing label.

yes, my logic too, but it doesn’t work! I replaced the "Description" column with the scripted version and it doesn't hide when using the script. I have tried various changes to the scripting and commented out any mapping related to "Description", and it still doesn't work. I note that adding the scripted column does result in the label changing to "Description", but the column will not hide when running your script on a file with an empty "Description" Column.

The solution might involve changing the label on the "Description" script to something else. Would that be a solution for the places where the column is scripted? That is, make sure that all column labels are unique. That way no matter what column is used/created, it can be programmed to hide.

I also noticed that your script does not work when files or folders are selected below the first level in the Lister. Hiding empty columns of the first level does follow through to the nested folders and files. This behaviour may be related to changes in DOPUS 13 as it no allows folders and files to be expanded from the Lister.

Yes, a unique label is the correct way to handle this.

Ah, yes. Expandable Folders... nothing but trouble with them buggers :smiley:

I'll check what changes are needed.

@lxp I think we could take advantage that PRINT command and tab.format.columns shows the columns in the same order? Or is there a scenario where it is not?

I thought that by using FLATVIEW=mixed with PRINT this point could be handled, but I see that it is more complicated than that, it is necessary to change the command's source for those cases.
Could you add to your request the possibility that FLATVIEW can affect nested items?

Anyway, playing with this a bit, I came out with this, from a quick test, it works, no matter the language DOpus is in or the origin of the column (built-in, script, Evaluator). It also works with expanded folders. @lxp @Renaldostheold could you take a look at it?

function OnInit(initData) {
    initData.name = 'RemoveEmptyColumns';
    initData.version = '2023-10-05';
    initData.url = 'https://resource.dopus.com/t/removeemptycolumns/43929';
    initData.desc = 'RemoveEmptyColumns';
    initData.default_enable = true;
    initData.min_version = '12.0';
}

function OnAddCommands(addCmdData) {
    var cmd = addCmdData.AddCommand();
    cmd.name = 'RemoveEmptyColumns';
    cmd.method = 'OnRemoveEmptyColumns';
    cmd.icon = 'script';
}

function OnRemoveEmptyColumns(scriptCommandData) {
    var cmd = scriptCommandData.func.command;
    var tab = scriptCommandData.func.sourcetab;

    cmd.deselect = false;

    if (tab.selected.count == 0) return;

    var fsu = DOpus.FSUtil();
    var paths = DOpus.Create().Vector();
    var cols = DOpus.Create().Vector();
    var fileDisplay = DOpus.Create().Vector();
    var cmdLine = '';

    //grab all visible columns into a vector
    for (var i = 0; i < tab.format.columns.count; i++)
        cols.push_back(tab.format.columns(i));

    //grab all paths, needed to work with expandable folders
    for (var i = 0; i < tab.selected.count; i++)
        paths.push_back(tab.selected(i).realpath.pathpart);

    //clear dupes in paths
    paths.unique();

    DOpus.Output('paths: ' + paths.count);
    var tmpFile = fsu.GetTempFilePath();

    for (var p = 0; p < paths.length; p++) {
        //change command's source to item path so PRINT would work even for nested items
        cmd.SetSource(paths(p));
        cmd.RunCommand('Print FOLDER=selected AS=tab QUIET ENCODING=utf8nobom TO="' + tmpFile + '"');
        var content = ReadFile(tmpFile);
        if (!content) {
            DOpus.Output('Could not print File Display!?');
            continue;
        }
        for (var i = 0; i < content.length; i++) {
            if (content[i].length == 0) continue;
            if (i == 0 && !fileDisplay.empty) continue; //only get the headers from first path
            fileDisplay.push_back(DOpus.Create().Vector(content[i].split('\t')));
        }
    }

    for (var k = 0; k < fileDisplay(0).length; k++) {
        var len = 0;
        for (var j = 1; j < fileDisplay.length; j++) {
            len += fileDisplay(j)(k).length;
        }
        if (len) continue;

        //PRINT use headers, not labels
        var header = fileDisplay(0)(k);
        // DOpus.Output('Header in column at pos' + k + ': ' + cols(k).header);
        // DOpus.Output('Header: ' + header);
        //Printed columns and column object should have the same order
        if (cols(k).header == header) {
            if (cmdLine) cmdLine += ',';
            cmdLine += cols(k).name;
        }
        else {
            DOpus.Output('Header not found: ' + header);
        }
    }
    if (!cmdLine) {
        DOpus.Output('Nothing to remove!');
        return;
    }

    cmdLine = 'Set COLUMNSREMOVE="' + cmdLine + '"';

    DOpus.Output(cmdLine);
    cmd.SetSourceTab(tab);
    cmd.RunCommand(cmdLine);
}

function ReadFile(filename) {
    var res = false;
    var FSU = DOpus.FSUtil();
    if (!FSU.Exists(filename))
        return res;
    var fh = FSU.OpenFile(filename);
    if (fh.error !== 0)
        DOpus.Output("An error occurred while opening file:" + filename);
    else {
        try {
            var blob = fh.Read();
            try {
                res = DOpus.Create.StringTools.Decode(blob, "utf-8");
            }
            catch (e) {
                DOpus.Output("Can't decode " + filename + ": " + e.description);
            }
            blob.Free();
        }
        catch (e) {
            DOpus.Output("Can't read " + filename + ": " + e.description);
        }
    }
    fh.Close();
    FSU = null;
    if (res) res = res.split('\r\n');
    return res;
}

This works to remove empty columns from any level. Thanks.

It also removed both Image Description and Description columns, if both are used and are empty.

However, it still won't remove some columns. Eg. the Scripted columns. I can live with that, but I think it is related to the way they those scripts are written and the columns named.

@Renaldostheold that's odd, I do try with some empty script columns and the command above works.
Don't hurt to uncomment this 2 lines:

DOpus.Output('Header in column at pos' + k + ': ' + cols(k).header);
DOpus.Output('Header: ' + header);

To see if the offended column is in a different order...
Obviously you have to open the Script Log Pane first

Update 2023-11-29:

  • The script should now be language-independent and reliably find all columns
  • Requires at least Opus 13.0.50
2 Likes