Source: js/foo_spider_monkey_panel.js | Spider Monkey Panel

    /**
     * @typedef {number} float
     */

    /**
     * Evaluates the script in file.<br>
     * Similar to `eval({@link utils.ReadTextFile}(path))`, but provides more features:<br>
     * - Has `include guards` - script won't be evaluated a second time if it was evaluated before in the same panel.<br>
     * - Has script caching - script file will be read only once from filesystem (even if it is included from different panels).<br>
     * - Has better error reporting.<br>
     * <br>
     * Note: when the relative `path` is used it will be evaluated to the following values:<br>
     * - `${fb.ComponentPath}/${path}`, if the method is invoked from a top-level script (i.e. panel's `Configure` dialog).<br>
     * - `${current_script_path}/${path}`, otherwise.
     *
     * @param {string} path Absolute or relative path to JavaScript file.
     * @param {object=} [options=undefined]
     * @param {boolean=} [options.always_evaluate=false] If true, evaluates the script even if it was included before.
     *
     * @example <caption>Include sample from `foo_spider_monkey_panel`</caption>
     * include('samples/complete/properties.js')
     */
    function include(path, options) { }

    /**
     * See {@link https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/clearTimeout}.
     *
     * @param {number} timerID
     */
    function clearTimeout(timerID) { } // (void)

    /**
     * See {@link https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/clearInterval}.
     *
     * @param {number} timerID
     */
    function clearInterval(timerID) { } // (void)

    /**
     * See {@link https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setInterval}.
     *
     * @param {function()} func
     * @param {number} delay
     * @param {...*} [func_args]
     * @return {number}
     */
    function setInterval(func, delay, ...func_args) { } // (uint)

    /**
     * See {@link https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout}.
     *
     * @param {function()} func
     * @param {number} delay
     * @param {...*} [func_args]
     * @return {number}
     *
     * @example
     * // See `samples/basic/Timer.js`
     */
    function setTimeout(func, delay, ...func_args) { } // (uint)

    /**
     * Load ActiveX object.
     *
     * @constructor
     * @param {string} name
     *
     * @example
     * const xmlhttp = new ActiveXObject('Microsoft.XMLHTTP');
     */
    function ActiveXObject(name) {

        /**
         * Creates an `ActiveXObject` that contains an object of type (VT_ARRAY|SOME_TYPE).
         *
         * @static
         *
         * @param {Array<*>} arr An array that contains elements of primitive type.
         * @param {number} element_variant_type A variant type of array elements.
         *
         * @return {ActiveXObject}
         *
         * @example
         * let filename = 'x:\\file.bin';
         * let bin_data = [0x01, 0x00, 0x00, 0x02]
         * let com_bin_data = ActiveXObject.ActiveX_CreateArray(bin_data, 0x11) // VT_UI1
         *
         * let stm = new ActiveXObject('ADODB.Stream');
         *
         * stm.Open();
         * stm.Type = 1; //adTypeBinary
         * stm.Write(com_bin_data);
         * stm.SaveToFile(filename, 2);
         * stm.Close();
         */
        this.ActiveX_CreateArray = function (arr, element_variant_type) { };

        /**
         * Emulates COM's weird behaviour of property accessors.
         *
         * @param {number|string} prop_name Name of the property or it's numeric index
         * @return {*}
         *
         * @example
         * some_activex.ActiveX_Get('property_name', 'additional_info').DoSmth();
         * // in COM:
         * // some_activex.Item('property_name', 'additional_info').DoSmth();
         */
        this.ActiveX_Get = function (prop_name) { };

        /**
         * Emulates COM's weird behaviour of property accessors.
         *
         * @param {number|string} prop_name Name of the property or it's numeric index
         *
         * @example
         * some_activex.ActiveX_Set('property_name', 'new_value', 'additional_info');
         * // in COM:
         * // some_activex.Item('property_name', 'additional_info') = "new_value";
         */
        this.ActiveX_Set = function (prop_name) { };
    }

    /**
     * See {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Microsoft_JavaScript_extensions/Enumerator}.
     *
     * @constructor
     * @param {ActiveXObject} active_x_object Any ActiveX collection object.
     */
    function Enumerator(active_x_object) {

        /**
         * Returns a Boolean value indicating if the enumerator is at the end of the collection.
         *
         * @return {boolean}
         */
        this.atEnd = function () { };

        /**
         * Returns the current item in the collection.
         *
         * @return {*}
         */
        this.item = function () { };

        /**
         * Resets the current item in the collection to the first item.
         *
         * @method
         */
        this.moveFirst = function () { };

        /**
         * Moves the current item to the next item in the collection.
         *
         * @method
         */
        this.moveNext = function () { };
    }

    /**
     * @namespace
     */
    let console = {
        /**
         * See {@link https://developer.mozilla.org/en-US/docs/Web/API/Console/log}
         *
         * @param {...*} var_args
         */
        log: function (...var_args) { }, // (void)
    };

    /**
     * Functions for controlling foobar2000 and accessing it's data.
     *
     * @namespace
     */
    let fb = {
        /**
         * @type {boolean}
         *
         * @example
         * fb.AlwaysOnTop = !fb.AlwaysOnTop; // Toggles the current value.
         */
        AlwaysOnTop: undefined, //(boolean) (read, write)

        /**
         * @type {string}
         * @readonly
         *
         * @example
         * console.log(fb.ComponentPath); // C:\Users\User\AppData\Roaming\foobar2000\user-components\foo_spider_monkey_panel\
         */
        ComponentPath: undefined, // (string) (read)

        /** @type {boolean} */
        CursorFollowPlayback: undefined, // (boolean) (read, write)

        /**
         * @type {string}
         * @readonly
         */
        FoobarPath: undefined, // (string) (read)

        /**
         * @type {boolean}
         * @readonly
         */
        IsPaused: undefined, // (boolean) (read)

        /**
         * @type {boolean}
         * @readonly
         */
        IsPlaying: undefined, // (boolean) (read)

        /** @type {boolean} */
        PlaybackFollowCursor: undefined, // (boolean) (read, write)

        /**
         * @type {float}
         * @readonly
         *
         * @example
         * console.log(fb.PlaybackLength); // 322.843414966166
         *
         * @example
         * console.log(Math.round(fb.PlaybackLength)); // 323
         */
        PlaybackLength: undefined, // (double) (read)

        /**
         * @type {float}
         *
         * @example
         * fb.PlaybackTime = 60; // Jumps to the 1 minute mark.
         */
        PlaybackTime: undefined, // (double) (read, write)

        /**
         * @type {string}
         * @readonly
         */
        ProfilePath: undefined, // (string) (read)

        /**
         * 0 - None<br>
         * 1 - Track<br>
         * 2 - Album<br>
         * 3 - Track/Album by Playback Order (only available in foobar2000 v1.3.8 and later)
         *
         * @type {number}
         */
        ReplaygainMode: undefined, // (uint) (read, write)

        /**
         * @type {boolean}
         *
         * @example
         * fb.StopAfterCurrent = !fb.StopAfterCurrent; // Toggles the current value.
         */
        StopAfterCurrent: undefined, // (boolean) (read, write)

        /**
         * @type {float}
         *
         * @example
         * fb.Volume = 0; // Sets the volume to max. -100 is the minimum.
         */
        Volume: undefined, // (float) (read, write),

        /**
         * @return {FbUiSelectionHolder}
         */
        AcquireUiSelectionHolder: function () { }, // (FbUiSelectionHolder)

        /** @method */
        AddDirectory: function () { }, // (void)

        /** @method */
        AddFiles: function () { }, // (void)

        /**
         * Checks Clipboard contents are handles or a file selection from Windows Explorer. Use in conjunction
         * with {@link fb.GetClipboardContents}.
         *
         * @return {boolean}
         */
        CheckClipboardContents: function () { }, // (boolean)

        /**
         * Clears active playlist.<br>
         * If you wish to clear a specific playlist, use {@link plman.ClearPlaylist}(playlistIndex).
         */
        ClearPlaylist: function () { }, // (void)

        /**
         * Note: items can then be pasted in other playlist viewers or in Windows Explorer as files.
         *
         * @param {FbMetadbHandleList} handle_list
         * @return {boolean}
         *
         * @example <caption>Copy playlist items</caption>
         * let handle_list = plman.GetPlaylistSelectedItems(plman.ActivePlaylist);
         * fb.CopyHandleListToClipboard(handle_list);
         *
         * @example <caption>Cut playlist items</caption>
         * let ap = plman.ActivePlaylist;
         * if (!plman.IsPlaylistLocked(ap)) {
         *    let handle_list = plman.GetPlaylistSelectedItems(ap);
         *    if (fb.CopyHandleListToClipboard(handle_list)) {
         *        plman.UndoBackup(ap);
         *        plman.RemovePlaylistSelection(ap);
         *    }
         *  }
         */
        CopyHandleListToClipboard: function (handle_list) { }, // (boolean)

        /**
         * @return {ContextMenuManager}
         *
         * @example
         * // See `samples/basic/MainMenuManager All-In-One.js`, `samples/basic/Menu Sample.js`
         */
        CreateContextMenuManager: function () { }, // (ContextMenuManager)

        /**
         * Returns an empty handle list.<br>
         * Deprecated: use {@link FbMetadbHandleList} constructor instead.
         *
         * @deprecated
         * @return {FbMetadbHandleList}
         */
        CreateHandleList: function () { }, // (FbMetadbHandleList)

        /**
         * @return {MainMenuManager}
         *
         * @example
         * // See `samples/basic/MainMenuManager All-In-One.js`, `samples/basic/Menu Sample.js`
         */
        CreateMainMenuManager: function () { }, // (MainMenuManager)

        /**
         * @param {string=} [name=''] Will be shown in console when used with {@link FbProfiler#Print} method.
         * @return {FbProfiler}
         */
        CreateProfiler: function (name) { }, // (FbProfiler) [name]

        /**
         * Invokes drag-n-drop operation (see {@link https://docs.microsoft.com/en-us/windows/win32/api/ole2/nf-ole2-dodragdrop}).<br>
         * <br>
         * Quick tips:<br>
         * - If you need only to drag from your panel with copy (i.e. without physically moving them):
         *      use only fb.DoDragDrop(handles, DROPEFFECT_COPY | DROPEFFECT_LINK).<br>
         * - If you need only to receive drop to your panel with copy:
         *      handle `on_drop_*()` callbacks, while setting action.effect argument to (DROPEFFECT_COPY | DROPEFFECT_LINK).<br>
         * <br>
         * Full drag-n-drop interface description:<br>
         * - Drag-n-drop interface is based on Microsoft IDropSource and IDropTarget interfaces, so a lot of info (including examples) could be gathered from MSDN (IDropSource, IDropTarget, DoDragDrop, DROPEFFECT).<br>
         * - Drag operation is started with DoDragDrop (whether it is called by your panel, or externally) with okEffects argument supplied.<br>
         * - DoDragDrop blocks code execution until the drag operation is finished (callbacks will be called properly though). It returns effect from Action.Effect from on_drag_drop after completion.<br>
         * - (Spider Monkey Panel specific) Drag operation is canceled when any mouse button is pressed.<br>
         * - (Spider Monkey Panel specific) All mouse callbacks are suppressed during drag operation (including on_mouse_lbtn_up, but excluding on_mouse_mbtn_up and on_mouse_rbtn_up).<br>
         * - Every drag callback receives Action argument. Action.Effect contains okEffects from DoDragDrop call. Action.Effect should be changed to the desired effect in the callback.
         *   If the returned Action.Effect was not in okEffects or is equal to DROPEFFECT_NONE (=== 0), then drop will be denied:
         *   cursor icon will be changed, on_drag_drop won't be called after releasing lmbtn, on_drag_leave will be called instead.<br>
         * - DROPEFFECT_LINK should be used as fallback in case effect argument does not have DROPEFFECT_COPY (===1), since some external drops only allow DROPEFFECT_LINK effect.<br>
         * - Changing effect on key modifiers is nice (to be in line with native Windows behaviour): see the example below.<br>
         *
         * @param {number} window_id see {@link window.ID}
         * @param {FbMetadbHandleList} handle_list
         * @param {number} effect Allowed effects.
         * @param {object=} [options=undefined] Customization options for the data displayed in the drag window.
         * @param {boolean=} [options.show_text=true] If true, will add track count text.
         * @param {boolean=} [options.use_album_art=true] If true, will use album art of the focused item from dragged tracks (if available)
         * @param {boolean=} [options.use_theming=true] If true, will use Windows drag window style. Album art and custom image are resized to fit when Windows style is active.
         * @param {?GdiBitmap=} [options.custom_image=undefined] Custom dragging image. Will be also displayed if use_album_art is true, but there is no album art available.
         * @return {number} Effect that was returned in {@link module:callbacks~on_drag_drop on_drag_drop}.
         *
         * @example
         * // See `samples/basic/DragnDrop.js`
         */
        DoDragDrop: function (window_id, handle_list, effect, options) { }, // (uint),

        /** @method */
        Exit: function () { }, // (void)

        /**
         * Note: clipboard contents can be handles copied to the clipboard in other components,
         * from {@link fb.CopyHandleListToClipboard} or a file selection, from Windows Explorer and etc.<br>
         * <br>
         * Performance note: validate clipboard content with {@link fb.CheckClipboardContents} before calling this method.
         *
         * @param {number} window_id
         * @return {FbMetadbHandleList}
         *
         * @example
         * function on_mouse_rbtn_up(x, y) {
         *    let ap = plman.ActivePlaylist;
         *    let menu = window.CreatePopupMenu();
         *    menu.AppendMenuItem(!plman.IsPlaylistLocked(ap) && fb.CheckClipboardContents() ? MF_STRING : MF_GRAYED, 1, "Paste"); // see Flags.js for MF_* definitions
         *    let idx = menu.TrackPopupMenu(x, y);
         *    if (idx == 1) {
         *        let handle_list  = fb.GetClipboardContents(window.ID);
         *        plman.InsertPlaylistItems(ap, plman.PlaylistItemCount(ap), handle_list );
         *    }
         *    return true;
         * }
         */
        GetClipboardContents: function (window_id) { }, // (FbMetadbHandleList)

        /**
         * Available only in foobar2000 v1.4 and above. Throws a script error on v1.3. * <br>
         * Returns a JSON array in string form so you need to use JSON.parse() on the result.
         *
         * @return {string}
         *
         * @example
         * let str = fb.GetDSPPresets();
         * let arr = JSON.parse(str);
         * console.log(JSON.stringify(arr, null, 4));
         * // [
         * //     {
         * //         "active": false,
         * //         "name": "High Filter"
         * //     },
         * //     {
         * //         "active": true,
         * //         "name": "R128 Compressor"
         * //     },
         * //     {
         * //         "active": false,
         * //         "name": "7.1 upmix"
         * //     }
         * // ]
         */
        GetDSPPresets: function () { },

        /**
         * @param {boolean=} [force=true] When true, it will use the first item of the active playlist if it is unable to get the focus item.
         * @return {FbMetadbHandle}
         */
        GetFocusItem: function (force) { }, // (FbMetadbHandle) [force]

        /**
         * Returns all Media Library items as a handle list.
         *
         * @return {FbMetadbHandleList}
         */
        GetLibraryItems: function () { }, // (FbMetadbHandleList)

        /**
         * Note: do not use this while looping through a handle list. Use {@link FbMetadbHandleList#GetLibraryRelativePaths} instead. <br>
         * <br>
         * Returns an empty string when used on track not in Media Library
         *
         * @param {FbMetadbHandle} handle
         * @return {string}
         *
         * @example
         * // The foobar2000 Media Library is configured to watch "D:\Music" and the
         * // path of the now playing item is "D:\Music\Albums\Artist\Some Album\Some Song.flac"
         * let handle = fb.GetNowPlaying();
         * console.log(fb.GetLibraryRelativePath(handle)); // Albums\Artist\Some Album\Some Song.flac*
         */
        GetLibraryRelativePath: function (handle) { }, // (string)

        /**
         * Get handle of the now playing track.
         *
         * @return {?FbMetadbHandle} null, if nothing is being played.
         */
        GetNowPlaying: function () { }, // (FbMetadbHandle)

        /**
         * Available only in foobar2000 v1.4 and above. Throws a script error on v1.3. * <br>
         * Returns a JSON array in string form so you need to use JSON.parse() on the result.
         *
         * @return {string}
         *
         * @example
         * let str = fb.GetOutputDevices();
         * let arr = JSON.parse(str);
         * console.log(JSON.stringify(arr, null, 4));
         * // [
         * //     {
         * //         "active": false,
         * //         "device_id": "{5243F9AD-C84F-4723-8194-0788FC021BCC}",
         * //         "name": "Null Output",
         * //         "output_id": "{EEEB07DE-C2C8-44C2-985C-C85856D96DA1}"
         * //     },
         * //     {
         * //         "active": true,
         * //         "device_id": "{00000000-0000-0000-0000-000000000000}",
         * //         "name": "Primary Sound Driver",
         * //         "output_id": "{D41D2423-FBB0-4635-B233-7054F79814AB}"
         * //     },
         * //     {
         * //         "active": false,
         * //         "device_id": "{1C4EC038-97DB-48E7-9C9A-05FDED46847B}",
         * //         "name": "Speakers (Sound Blaster Z)",
         * //         "output_id": "{D41D2423-FBB0-4635-B233-7054F79814AB}"
         * //     },
         * //     {
         * //         "active": false,
         * //         "device_id": "{41B86272-3D6C-4A5A-8907-4FE7EBE39E7E}",
         * //         "name": "SPDIF-Out (Sound Blaster Z)",
         * //         "output_id": "{D41D2423-FBB0-4635-B233-7054F79814AB}"
         * //     },
         * //     {
         * //         "active": false,
         * //         "device_id": "{9CDC0FAE-2870-4AFA-8287-E86099D69076}",
         * //         "name": "3 - BenQ BL3200 (AMD High Definition Audio Device)",
         * //         "output_id": "{D41D2423-FBB0-4635-B233-7054F79814AB}"
         * //     }
         * // ]
         * // As you can see, only one of the items in the array has "active"
         * // set to true so that is the device you'd want to display the name of
         * // or mark as selected in a menu.
         */
        GetOutputDevices: function () { }, // (string)

        /**
         * Note: use try/catch to handle invalid queries. An empty handle list will be returned if the query
         * is valid but there are no results.
         *
         * @param {FbMetadbHandleList} handle_list
         * @param {string} query
         * @return {FbMetadbHandleList} Unsorted results.
         *
         * @example
         * let a = fb.GetQueryItems(plman.GetPlaylistItems(plman.ActivePlaylist), "rating IS 5");
         *
         * @example
         * let b = fb.GetQueryItems(fb.GetLibraryItems(), "rating IS 5");
         */
        GetQueryItems: function (handle_list, query) { }, // (FbMetadbHandleList)

        /**
         * Gets now playing or selected item according to settings in "File>Preferences>Display>Selection viewers".
         *
         * @return {?FbMetadbHandle}
         */
        GetSelection: function () { }, // (FbMetadbHandle)

        /**
         * Works like {@link fb.GetSelection}, but returns a handle list.<br>
         *
         * @param {number=} [flags=0] 1 - no now playing
         * @return {FbMetadbHandleList}
         */
        GetSelections: function (flags) { }, // (FbMetadbHandleList) //[flags]

        /**
         * Retrieves what the selection type is.
         *
         * @return {number}
         *     0 - undefined (no item)<br>
         *     1 - active_playlist_selection<br>
         *     2 - caller_active_playlist<br>
         *     3 - playlist_manager<br>
         *     4 - now_playing<br>
         *     5 - keyboard_shortcut_list<br>
         *     6 - media_library_viewer
         */
        GetSelectionType: function () { }, // (uint)

        /**
         * @return {boolean}
         */
        IsLibraryEnabled: function () { }, // (boolean)

        /**
         * Performance note: don't use in `on_paint`.
         *
         * @param {string} command Path to main menu item
         * @return {boolean} true, if the item is checked.
         *
         * @example
         * fb.RunMainMenuCommand("Playback/Scrobble Tracks"); // available with foo_scrobble
         */
        IsMainMenuCommandChecked: function (command) { }, // (boolean)

        /**
         * @param {FbMetadbHandle} handle
         * @return {boolean}
         *
         * @example
         * let np = fb.GetNowplaying();
         * console.log(fb.IsMetadbInMediaLibrary(np)); // If false, playing track is not in Media Library.
         */
        IsMetadbInMediaLibrary: function (handle) { }, // (boolean)

        /**
         * Loads playlist from file. Equivalent to `File`>`Load Playlist...`.
         *
         * @method
         */
        LoadPlaylist: function () { }, // (void)

        /** @method */
        Next: function () { }, // (void)

        /** @method */
        Pause: function () { }, // (void)

        /** @method */
        Play: function () { }, // (void)

        /** @method */
        PlayOrPause: function () { }, // (void)

        /** @method */
        Prev: function () { }, // (void)

        /** @method */
        Random: function () { }, // (void)

        /**
         * Shows context menu for currently played track.
         *
         * @param {string} command
         * @param {number=} [flags=0]
         *     0 - default (depends on whether SHIFT key is pressed, flag_view_reduced or flag_view_full is selected)<br>
         *     4 - flag_view_reduced<br>
         *     8 - flag_view_full. This can be useful if you need to run context commands the user may have hidden
         *         using File>Preferences>Display>Context Menu<br>
         * @return {boolean}
         *
         * @example
         * fb.RunContextCommand("Properties");
         */
        RunContextCommand: function (command, flags) { }, // (boolean) [, flags]

        /**
         * Shows context menu for supplied tracks.
         *
         * @param {string} command
         * @param {FbMetadbHandle|FbMetadbHandleList} handle_or_handle_list Handles on which to apply context menu
         * @param {number=} flags Same flags as {@link fb.RunContextCommand}
         * @return {boolean}
         */
        RunContextCommandWithMetadb: function (command, handle_or_handle_list, flags) { }, // (boolean) [, flags]

        /**
         * @param {string} command
         * @return {boolean}
         *
         * @example
         * fb.RunMainMenuCommand("File/Add Location...");
         */
        RunMainMenuCommand: function (command) { }, // (boolean)

        /** @method */
        SavePlaylist: function () { }, // (void)

        /**
         * Available only in foobar2000 v1.4 and above. Throws a script error on v1.3.<br>
         * See {@link fb.GetDSPPresets}.
         *
         * @param {number} idx
         *
         * @example
         * let str = fb.GetDSPPresets();
         * let arr = JSON.parse(str);
         * let idx; // find the required DSP from `arr` and assign it to `idx`
         * fb.SetDSPPreset(idx);
         */
        SetDSPPreset: function (idx) { }, // (void)

        /**
         * Available only in foobar2000 v1.4 and above. Throws a script error on v1.3.<br>
         * See {@link fb.GetOutputDevices}.
         *
         * @param {string} output
         * @param {string} device
         *
         * @example
         * // To actually change device, you'll need the device_id and output_id
         * // and use them with fb.SetOutputDevice.
         * let str = fb.GetOutputDevices();
         * let arr = JSON.parse(str);
         * // Assuming same list from above, switch output to the last device.
         * fb.SetOutputDevice(arr[4].output_id, arr[4].device_id);
         */
        SetOutputDevice: function (output, device) { }, // (void)

        /** @method */
        ShowConsole: function () { }, // (void)

        /**
         * Opens the Library>Search window populated with the query you set.
         *
         * @param {string} query
         */
        ShowLibrarySearchUI: function (query) { }, // (void)

        /**
         * @param {string} message
         * @param {string=} [title='Spider Monkey Panel']
         */
        ShowPopupMessage: function (message, title) { }, // (void) [, title]

        /** @method */
        ShowPreferences: function () { }, // (void)

        /** @method */
        Stop: function () { }, // (void)

        /**
         * Performance note: if you use the same query frequently,
         * try caching FbTitleFormat object (by storing it somewhere),
         * instead of creating it every time.
         *
         * @param {string} expression
         * @return {FbTitleFormat}
         */
        TitleFormat: function (expression) { }, // (FbTitleFormat)

        /** @method */
        VolumeDown: function () { }, // (void)

        /** @method */
        VolumeMute: function () { }, // (void)

        /** @method */
        VolumeUp: function () { }, // (void)
    };

    /**
     * Functions for working with graphics. Most of them are wrappers for Gdi and GdiPlus methods.
     *
     * @namespace
     */
    let gdi = {

        /**
         * @param {number} w
         * @param {number} h
         * @return {GdiBitmap}
         */
        CreateImage: function (w, h) { }, // (GdiBitmap)

        /**
         * Performance note: avoid using inside `on_paint`.<br>
         * Performance note II: try caching and reusing `GdiFont` objects,
         * since the maximum amount of such objects is hard-limited by Windows.
         * `GdiFont` creation will fail after reaching this limit.
         *
         * @param {string} name
         * @param {number} size_px See Helper.js > Point2Pixel function for conversions
         * @param {number=} [style=0] See Flags.js > FontStyle
         * @return {?GdiFont} null, if font is not present.
         */
        Font: function (name, size_px, style) { }, // (GdiFont) [, style]

        /**
         * Load image from file.<br>
         * <br>
         * Performance note: consider using {@link gdi.LoadImageAsync} or {@link gdi.LoadImageAsyncV2} if there are a lot of images to load
         * or if the image is big.
         *
         * @param {string} path
         * @return {?GdiBitmap} null, if image failed to load.
         *
         * @example
         * let img = gdi.Image('e:\\images folder\\my_image.png');
         */
        Image: function (path) { }, // (GdiBitmap)

        /**
         * Load image from file asynchronously.
         *
         * @param {number} window_id see {@link window.ID}
         * @param {string} path
         * @return {number} a unique id, which is used in {@link module:callbacks~on_load_image_done on_load_image_done}.
         *
         * @example
         * // See `samples/basic/LoadImageAsync.js`
         */
        LoadImageAsync: function (window_id, path) { }, // (uint)

        /**
         * Load image from file asynchronously.
         * Returns a `Promise` object, which will be resolved when image loading is done.
         *
         * @param {number} window_id see {@link window.ID}
         * @param {string} path
         * @return {Promise.<?GdiBitmap>}
         *
         * @example
         * // See `samples/basic/LoadImageAsyncV2.js`
         */
        LoadImageAsyncV2: function (window_id, path) { }
    };

    /**
     * Functions for managing foobar2000 playlists.
     *
     * @namespace
     */
    let plman = {

        /**
         * -1 if there is no active playlist.
         *
         * @type {number}
         *
         * @example
         * console.log(plman.ActivePlaylist);
         *
         * @example
         * plman.ActivePlaylist = 1; // Switches to 2nd playlist.
         */
        ActivePlaylist: undefined, // (int) (read, write)

        /**
         * 0 - Default<br>
         * 1 - Repeat (Playlist)<br>
         * 2 - Repeat (Track)<br>
         * 3 - Random<br>
         * 4 - Shuffle (tracks)<br>
         * 5 - Shuffle (albums)<br>
         * 6 - Shuffle (folders)
         *
         * @type {number}
         */
        PlaybackOrder: undefined, // (uint) (read, write)

        /**
         * -1 if there is no playing playlist.
         *
         * @type {number}
         *
         * @example
         * console.log(plman.PlayingPlaylist);
         */
        PlayingPlaylist: undefined, // (int) (read, write)

        /**
         * @type {number}
         * @readonly
         */
        PlaylistCount: undefined, // (uint) (read)

        /**
         * A Recycle Bin for playlists.
         *
         * @type {FbPlaylistRecycler}
         * @readonly
         */
        PlaylistRecycler: undefined, // (FbPlaylistRecycler) (read)

        /**
         * This operation is asynchronous and may take some time to complete if it's a large array.
         *
         * @param {number} playlistIndex
         * @param {Array<string>} paths An array of files/URLs
         * @param {boolean=} [select=false]
         *        If true, the active playlist will be set to the playlistIndex, the items will
         *        be selected and focus will be set to the first new item.
         *
         * @example
         * plman.AddLocations(plman.ActivePlaylist, ["e:\\1.mp3"]);
         * // This operation is asynchronous, so any code in your script directly
         * // after this line will run immediately without waiting for the job to finish.
         */
        AddLocations: function (playlistIndex, paths, select) { }, // (void) [, select]

        /**
         * @param {number} playlistIndex
         *
         * @example
         * plman.ClearPlaylist(plman.PlayingPlaylist);
         */
        ClearPlaylist: function (playlistIndex) { }, // (void)

        /**
         * @param {number} playlistIndex
         *
         * @example
         * plman.ClearPlaylistSelection(plman.ActivePlaylist);
         */
        ClearPlaylistSelection: function (playlistIndex) { }, // (void)

        /**
         * @param {number} playlistIndex
         * @param {string} name Name for the new autoplaylist.
         * @param {string} query Title formatting pattern for forming the playlist content.
         * @param {string=} [sort=''] Title formatting pattern for sorting.
         * @param {number=} [flags=0] 1 - when set, will keep the autoplaylist sorted and prevent user from reordering it.
         * @return {number} Index of the created playlist.
         */
        CreateAutoPlaylist: function (playlistIndex, name, query, sort, flags) { }, // (uint) [, sort][, flags]

        /**
         * @param {number} playlistIndex
         * @param {string} name
         * @return {number} Index of the created playlist.
         *
         * @example
         * // Creates a new playlist named "New playlist", which is put at the beginning of the current playlists.
         * plman.CreatePlaylist(0, '');
         *
         * @example
         * // Create a new playlist named "my favourites", which is put at the end.
         * plman.CreatePlaylist(plman.PlaylistCount, 'my favourites');
         */
        CreatePlaylist: function (playlistIndex, name) { }, // (uint)

        /**
         * Note: the duplicated playlist gets inserted directly after the source playlistIndex.<br>
         * It only duplicates playlist content, not the properties of the playlist (e.g. Autoplaylist).
         *
         * @param {number} playlistIndex
         * @param {?string=} [name] A name for the new playlist. If the name is "" or undefined, the name of the source playlist will be used.
         * @return {number} Index of the created playlist.
         */
        DuplicatePlaylist: function (playlistIndex, name) { }, // (uint)

        /**
         * Signals playlist viewers to display the track (e.g. by scrolling to it's position).
         *
         * @param {number} playlistIndex
         * @param {number} playlistItemIndex
         */
        EnsurePlaylistItemVisible: function (playlistIndex, playlistItemIndex) { }, // (void)

        /**
         * Starts playback by executing default doubleclick/enter action unless overridden by a lock to do something else.
         *
         * @param {number} playlistIndex
         * @param {number} playlistItemIndex
         * @return {boolean} -1 on failure.
         */
        ExecutePlaylistDefaultAction: function (playlistIndex, playlistItemIndex) { }, // (boolean)

        /**
         * Returns playlist index of the named playlist or creates a new one, if not found.<br>
         * If a new playlist is created, the playlist index of that will be returned.
         *
         * @param {string} name
         * @param {boolean} unlocked If true, locked playlists are ignored when looking for existing playlists.
         *                           If false, the playlistIndex of any playlist with the matching name will be returned.
         * @return {number} Index of the found or created playlist.
         */
        FindOrCreatePlaylist: function (name, unlocked) { }, // (uint)

        /**
         * @param {string} name Case insensitive.
         * @return {number} Index of the found playlist on success, -1 on failure.
         */
        FindPlaylist: function (name) { }, // (int)

        /**
         * Retrieves playlist position of currently playing item.<br>
         * On failure, the property {@link FbPlayingItemLocation#IsValid} will be set to false.
         *
         * @return {FbPlayingItemLocation}
         */
        GetPlayingItemLocation: function () { }, // (FbPlayingItemLocation)

        /**
         * @param {number} playlistIndex
         * @return {number} Returns -1 if nothing is selected
         *
         * @example
         * let focus_item_index = plman.GetPlaylistFocusItemIndex(plman.ActivePlaylist); // 0 would be the first item
         */
        GetPlaylistFocusItemIndex: function (playlistIndex) { }, // (int)

        /**
         * @param {number} playlistIndex
         * @return {FbMetadbHandleList}
         *
         * @example
         * let handle_list = plman.GetPlaylistItems(plman.PlayingPlaylist);
         */
        GetPlaylistItems: function (playlistIndex) { }, // (FbMetadbHandleList)

        /**
         * @param {number} playlistIndex
         * @return {string}
         *
         * @example
         * console.log(plman.GetPlaylistName(plman.ActivePlaylist));
         */
        GetPlaylistName: function (playlistIndex) { }, // (string)

        /**
         * @param {number} playlistIndex
         * @return {FbMetadbHandleList}
         *
         * @example
         * let selected_items = plman.GetPlaylistSelectedItems(plman.ActivePlaylist);
         */
        GetPlaylistSelectedItems: function (playlistIndex) { }, // (FbMetadbHandleList)

        /**
         * @param {number} playlistIndex
         * @param {number} base Position in playlist
         * @param {FbMetadbHandleList} handle_list Items to insert
         * @param {boolean=} [select=false] If true then inserted items will be selected
         *
         * @example <caption>Add all library tracks to the beginning of playlist.</caption>
         * let ap = plman.ActivePlaylist;
         * plman.InsertPlaylistItems(ap, 0, fb.GetLibraryItems());
         *
         * @example <caption>Add all library tracks to end of playlist.</caption>
         * let ap = plman.ActivePlaylist;
         * plman.InsertPlaylistItems(ap, plman.PlaylistItemCount(ap), fb.GetLibraryItems());
         */
        InsertPlaylistItems: function (playlistIndex, base, handle_list, select) { }, // (void) [, select]

        /**
         * Same as {@link plman.InsertPlaylistItems} except any duplicates contained in handle_list are removed.
         *
         * @param {number} playlistIndex
         * @param {number} base Position in playlist
         * @param {FbMetadbHandleList} handle_list Items to insert
         * @param {boolean=} [select=false] If true then inserted items will be selected
         */
        InsertPlaylistItemsFilter: function (playlistIndex, base, handle_list, select) { }, // (void) select = false

        /**
         * @param {number} playlistIndex
         * @return {boolean}
         */
        IsAutoPlaylist: function (playlistIndex) { }, // (boolean)

        /**
         * @param {number} playlistIndex
         * @param {number} playlistItemIndex
         * @return {boolean}
         */
        IsPlaylistItemSelected: function (playlistIndex, playlistItemIndex) { }, // (boolean)

        /**
         * Note: returns true, if the playlist is an autoplaylist. To determine if a playlist is not an autoplaylist,
         * but locked with something like `foo_utils` or `foo_playlist_attributes`, use with conjunction of {@link plman.IsAutoPlaylist}.
         *
         * @param {number} playlistIndex
         * @return {boolean}
         */
        IsPlaylistLocked: function (playlistIndex) { }, // (boolean)

        /**
         * @param {number} from
         * @param {number} to
         * @return {boolean}
         */
        MovePlaylist: function (from, to) { }, // (boolean)

        /**
         * @param {number} playlistIndex
         * @param {number} delta
         * @return {boolean}
         *
         * @example
         * // Moves selected items to end of playlist.
         * plman.MovePlaylistSelection(plman.ActivePlaylist, plman.PlaylistItemCount(plman.ActivePlaylist));
         */
        MovePlaylistSelection: function (playlistIndex, delta) { }, // (boolean)

        /**
         * @param {number} playlistIndex
         * @return {number}
         *
         * @example
         * console.log(plman.PlaylistItemCount(plman.PlayingPlaylist)); // 12
         */
        PlaylistItemCount: function (playlistIndex) { }, // (uint) (read)

        /**
         * Removes the specified playlist.<br>
         * Note: if removing the active playlist, no playlist will be active after using this. You'll
         * need to set it manually or use {@link plman.RemovePlaylistSwitch} instead.
         *
         * @param {number} playlistIndex
         * @return {boolean}
         */
        RemovePlaylist: function (playlistIndex) { }, // (boolean)

        /**
         * @param {number} playlistIndex
         * @param {boolean=} [crop=false] If true, then removes items that are NOT selected.
         *
         * @example <Remove selected items from playlist>
         * plman.RemovePlaylistSelection(plman.ActivePlaylist);
         *
         * @example <Remove items that are NOT selected>
         * plman.RemovePlaylistSelection(plman.ActivePlaylist, true);
         */
        RemovePlaylistSelection: function (playlistIndex, crop) { }, // (void) [, crop]

        /**
         * Removes the specified playlist.<br>
         * This automatically sets another playlist as active if removing the active playlist.
         *
         * @param {number} playlistIndex
         * @return {boolean}
         */
        RemovePlaylistSwitch: function (playlistIndex) { }, // (boolean)

        /**
         * @param {number} playlistIndex
         * @param {string} name
         * @return {boolean}
         */
        RenamePlaylist: function (playlistIndex, name) { }, // (boolean)

        /**
         * Workaround so you can use the Edit menu or run {@link fb.RunMainMenuCommand}("Edit/Something...")
         * when your panel has focus and a dedicated playlist viewer doesn't.
         *
         * @example
         * plman.SetActivePlaylistContext(); // once on startup
         *
         * function on_focus(is_focused) {
         *    if (is_focused) {
         *        plman.SetActivePlaylistContext(); // When the panel gets focus but not on every click
         *    }
         * }
         */
        SetActivePlaylistContext: function () { }, // (void)

        /**
         * @param {number} playlistIndex
         * @param {number} playlistItemIndex
         *
         * @example
         * plman.SetPlaylistFocusItem(plman.ActivePlaylist, 0);
         */
        SetPlaylistFocusItem: function (playlistIndex, playlistItemIndex) { }, // (void)

        /**
         * @param {number} playlistIndex
         * @param {FbMetadbHandle} handle
         *
         * @example
         * let ap = plman.ActivePlaylist;
         * let handle = plman.GetPlaylistItems(ap)[1]; // 2nd item in playlist
         * plman.SetPlaylistFocusItemByHandle(ap, handle);
         */
        SetPlaylistFocusItemByHandle: function (playlistIndex, handle) { }, // (void)

        /**
         * @param {number} playlistIndex
         * @param {Array<number>} affectedItems An array of item indexes.
         * @param {boolean} state
         *
         * @example
         * // Selects first, third and fifth tracks in playlist. This does not affect other selected items.
         * plman.SetPlaylistSelection(plman.ActivePlaylist, [0, 2, 4], true);
         */
        SetPlaylistSelection: function (playlistIndex, affectedItems, state) { }, // (void)

        /**
         * @param {number} playlistIndex
         * @param {number} playlistItemIndex
         * @param {boolean} state
         *
         * @example
         * // Deselects first playlist item. Only works when it is already selected!
         * plman.SetPlaylistSelectionSingle(plman.ActivePlaylist, 0, false);
         *
         * @example
         * let ap = plman.ActivePlaylist;
         * // Selects last item in playlist. This does not affect other selected items.
         * plman.SetPlaylistSelectionSingle(ap, plman.PlaylistItemCount(ap) - 1, true);
         */
        SetPlaylistSelectionSingle: function (playlistIndex, playlistItemIndex, state) { }, // (void)

        /**
         * Shows popup window letting you edit certain autoplaylist properties.<br>
         * Before using, check if your playlist is an autoplaylist by using {@link plman.IsAutoPlaylist};
         *
         * @param {number} playlistIndex
         * @return {boolean}
         *
         * @example
         * fb.ShowAutoPlaylistUI(plman.ActivePlaylist);
         */
        ShowAutoPlaylistUI: function (playlistIndex) { }, // (boolean)

        /**
         * @param {number} playlistIndex Index of playlist to alter.
         * @param {string} pattern Title formatting pattern to sort by. Set to "" to randomise the order of items.
         * @param {boolean=} [selected_items_only=false]
         * @return {boolean} true on success, false on failure (playlist locked etc).
         */
        SortByFormat: function (playlistIndex, pattern, selected_items_only) { }, // (boolean) [, selected_items_only]

        /**
         * @param {number} playlistIndex Index of playlist to alter.
         * @param {string} pattern Title formatting pattern to sort by.
         * @param {number=} [direction=1]
         *     1 - ascending<br>
         *     -1 - descending<br>
         * @return {boolean}
         */
        SortByFormatV2: function (playlistIndex, pattern, direction) { }, // (boolean) [, direction]

        /**
         * @param {number=} [direction=1]
         *     1 - ascending<br>
         *     -1 - descending<br>
         */
        SortPlaylistsByName: function (direction) { }, //(void)

        /**
         * Saves playlist's current state: this will enable `Edit`>`Undo` menu item after calling other {@link plman} methods that change playlist content.<br>
         * Note: this method should be called before performing modification to the playlist.
         *
         * @param {number} playlistIndex
         */
        UndoBackup: function (playlistIndex) { }, // (void)

        /**
         * @param {FbMetadbHandle} handle
         */
        AddItemToPlaybackQueue: function (handle) { }, // (void)

        /**
         * @param {number} playlistIndex
         * @param {number} playlistItemIndex
         */
        AddPlaylistItemToPlaybackQueue: function (playlistIndex, playlistItemIndex) { }, // (void)

        /**
         * @param {FbMetadbHandle} handle
         * @param {number} playlistIndex
         * @param {number} playlistItemIndex
         * @return {number} Returns position in queue on success, -1 if track is not in queue.
         */
        FindPlaybackQueueItemIndex: function (handle, playlistIndex, playlistItemIndex) { }, // (int)

        /** @method */
        FlushPlaybackQueue: function () { }, // (void)

        /**
         * @return {Array<FbPlaybackQueueItem>}
         *
         * @example
         * let contents = plman.GetPlaybackQueueContents();
         * if (contents.length) {
         *     // access properties of first item
         *     console.log(contents[0].PlaylistIndex, contents[0].PlaylistItemIndex);
         * }
         */
        GetPlaybackQueueContents: function () { }, // (Array)

        /**
         * @return {FbMetadbHandleList}
         *
         * @example
         * let handles = plman.GetPlaybackQueueHandles();
         * if (handles.Count > 0) {
         *    // use "Count" to determine if Playback Queue is active.
         * }
         */
        GetPlaybackQueueHandles: function () { }, // ((FbMetadbHandleList))

        /**
         * @param {number} index
         */
        RemoveItemFromPlaybackQueue: function (index) { }, // (void)

        /**
         * @param {Array<number>} affectedItems Array like [1, 3, 5]
         */
        RemoveItemsFromPlaybackQueue: function (affectedItems) { }, // (void)
    };

    /**
     * Various utility functions.
     *
     * @namespace
     */
    let utils = {

        /**
         * A string corresponding to the version.
         *
         * Component uses semantic versioning (see {@link https://semver.org}).
         *
         * @type {string}
         *
         * @example
         * function is_compatible(requiredVersionStr) {
         *     let requiredVersion = requiredVersionStr.split('.');
         *     let currentVersion = utils.Version.split('.'); // e.g. 0.1.0-alpha.2
         *     if (currentVersion.length > 3) {
         *         currentVersion.length = 3; // We need only numbers
         *     }
         *
         *     for(let i = 0; i< currentVersion.length; ++i) {
         *       if (currentVersion[i] != requiredVersion[i]) {
         *           return currentVersion[i] > requiredVersion[i];
         *       }
         *     }
         *
         *     return true;
         * }
         *
         * let requiredVersionStr = '1.0.0';
         * if (!is_compatible(requiredVersionStr)) {
         *     fb.ShowPopupMessage(`This script requires v${requiredVersionStr}. Current component version is v${utils.Version}.`);
         * }
         */
        Version: undefined, // (string) (read)

        /**
         * Checks the availability of foobar2000 component.
         *
         * @param {string} name
         * @param {boolean=} [is_dll=true] If true, method checks filename as well as the internal name.
         * @return {boolean}
         *
         * @example
         * console.log(utils.CheckComponent("foo_playcount", true));
         */
        CheckComponent: function (name, is_dll) { }, //(boolean)

        /**
         * Check if the font is installed.<br>
         * Note: it cannot detect fonts loaded by `foo_ui_hacks`. However, {@link gdi.Font} can use those fonts.
         *
         * @param {string} name Can be either in English or the localised name in your OS.
         * @return {boolean}
         */
        CheckFont: function (name) { }, // (boolean)

        /**
         * Spawns a windows popup dialog to let you choose a colour.
         *
         * @param {number} window_id {@link window.ID}
         * @param {number} default_colour This colour is used if OK button was not clicked.
         * @return {number}
         *
         * @example
         * let colour = utils.ColourPicker(window.ID, RGB(255, 0, 0));
         * // See docs\Helper.js for RGB function.
         */
        ColourPicker: function (window_id, default_colour) { }, // (uint)

        /**
         * Various utility functions for working with file.
         *
         * @param {string} path
         * @param {string} mode
         *     "chardet" - Detects the codepage of the given file. Returns a corresponding codepage number on success, 0 if codepage detection failed.<br>
         *     "e" - If file path exists, returns true.<br>
         *     "s" - Retrieves file size, in bytes.<br>
         *     "d" - If path is a directory, returns true.<br>
         *     "split" - Returns an array of [directory, filename, filename_extension].
         * @return {*}
         *
         * @example
         * let arr = utils.FileTest("D:\\Somedir\\Somefile.txt", "split");
         * // arr[0] <= "D:\\Somedir\\" (always includes backslash at the end)
         * // arr[1] <= "Somefile"
         * // arr[2] <= ".txt"
         */
        FileTest: function (path, mode) { }, // (VARIANT)

        /**
         * @param {number} seconds
         * @return {string}
         *
         * @example
         * console.log(utils.FormatDuration(plman.GetPlaylistItems(plman.ActivePlaylist).CalcTotalDuration())); // 1wk 1d 17:25:30
         */
        FormatDuration: function (seconds) { }, // (string)

        /**
         * @param {number} bytes
         * @return {string}
         *
         * @example
         * console.log(utils.FormatFileSize(plman.GetPlaylistItems(plman.ActivePlaylist).CalcTotalSize())); // 7.9 GB
         */
        FormatFileSize: function (bytes) { }, // (string)

        /**
         * Load art image for the track asynchronously.<br>
         * <br>
         * Performance note: consider using {@link gdi.LoadImageAsync} or {@link gdi.LoadImageAsyncV2} if there are a lot of images to load
         * or if the image is big.
         *
         * @param {number} window_id {@link window.ID}
         * @param {FbMetadbHandle} handle
         * @param {number=} [art_id=0] See Flags.js > AlbumArtId
         * @param {boolean=} [need_stub=true]
         * @param {boolean=} [only_embed=false]
         * @param {boolean=} [no_load=false]  If true, "image" parameter will be null in {@link module:callbacks~on_get_album_art_done on_get_album_art_done} callback.
         *
         * @example
         * // See `samples/basic/GetAlbumArtAsync.js`
         */
        GetAlbumArtAsync: function (window_id, handle, art_id, need_stub, only_embed, no_load) { }, // (void) [, art_id][, need_stub][, only_embed][, no_load]

        /**
         * @typedef {Object} ArtPromiseResult
         * @property {?GdiBitmap} image null on failure
         * @property {string} path path to image file (or track file if image is embedded)
         */

        /**
         * Load art image for the track asynchronously.<br>
         * Returns a `Promise` object, which will be resolved when art loading is done.
         *
         * @param {number} window_id {@link window.ID}
         * @param {FbMetadbHandle} handle
         * @param {number=} [art_id=0] See Flags.js > AlbumArtId
         * @param {boolean=} [need_stub=true] If true, will return a stub image from `Preferences`>`Display`>`Stub image path` when there is no art image available.
         * @param {boolean=} [only_embed=false] If true, will only try to load the embedded image.
         * @param {boolean=} [no_load=false] If true, then no art loading will be performed and only path to art will be returned in {@link ArtPromiseResult}.
         * @return {Promise.<ArtPromiseResult>}
         *
         * @example
         * // See `samples/basic/GetAlbumArtAsyncV2.js`
         */
        GetAlbumArtAsyncV2: function (window_id, handle, art_id, need_stub, only_embed, no_load) { },

        /**
         * Load embedded art image for the track.<br>
         * <br>
         * Performance note: consider using {@link fb.GetAlbumArtAsync} or {@link fb.GetAlbumArtAsyncV2} if there are a lot of images to load.
         *
         * @param {string} rawpath Path to track file
         * @param {number=} [art_id=0] See Flags.js > AlbumArtId
         * @return {GdiBitmap}
         *
         * @example
         * let img = utils.GetAlbumArtEmbedded(fb.GetNowPlaying().RawPath, 0);
         */
        GetAlbumArtEmbedded: function (rawpath, art_id) { }, // (GdiBitmap) [, art_id]

        /**
         * Load art image for the track.<br>
         * <br>
         * Performance note: consider using {@link fb.GetAlbumArtAsync} or {@link fb.GetAlbumArtAsyncV2} if there are a lot of images to load.
         *
         * @param {FbMetadbHandle} handle
         * @param {number=} [art_id=0] See Flags.js > AlbumArtId
         * @param {boolean=} [need_stub=true]
         * @return {GdiBitmap}
         *
         * @example
         * // See `samples/basic/GetAlbumArtV2.js`
         */
        GetAlbumArtV2: function (handle, art_id, need_stub) { }, // (GdiBitmap) [, art_id][, need_stub]

        /**
         * @param {number} index {@link https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getsyscolor}
         * @return {number} 0 if failed
         *
         * @example
         * let splitter_colour = utils.GetSysColour(15);
         */
        GetSysColour: function (index) { }, // (uint)

        /**
         * @param {number} index {@link https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getsyscolor}
         * @return {number} 0 if failed
         */
        GetSystemMetrics: function (index) { }, // (int)

        /**
         * Retrieves filepaths that match the supplied pattern.
         *
         * @param {string} pattern
         * @param {number=} [exc_mask=0x10] Default is FILE_ATTRIBUTE_DIRECTORY. See Flags.js > Used in utils.Glob()
         * @param {number=} [inc_mask=0xffffffff]
         * @return {Array<string>}
         *
         * @example
         * let arr = utils.Glob("C:\\*.*");
         */
        Glob: function (pattern, exc_mask, inc_mask) { }, // (Array) [, exc_mask][, inc_mask]

        /**
         * @param {number} window_id
         * @param {string} prompt
         * @param {string} caption
         * @param {string=} [default_val='']
         * @param {boolean=} [error_on_cancel=false] If set to true, use try/catch like Example2.
         * @return {string}
         *
         * @example
         * // With "error_on_cancel" not set (or set to false), cancelling the dialog will return "default_val".
         * let username = utils.InputBox(window.ID, "Enter your username", "Spider Monkey Panel", "");
         *
         * @example
         * // Using Example1, you can't tell if OK or Cancel was pressed if the return value is the same
         * // as "default_val". If you need to know, set "error_on_cancel" to true which throws a script error
         * // when Cancel is pressed.
         * let username = "";
         * try {
         *    username = utils.InputBox(window.ID, "Enter your username", "Spider Monkey Panel", "", true);
         *    // OK was pressed.
         * } catch(e) {
         *     // Dialog was closed by pressing Esc, Cancel or the Close button.
         * }
         */
        InputBox: function (window_id, prompt, caption, default_val, error_on_cancel) { }, // (string)

        /**
         * @param {number} vkey {@link https://docs.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes}. Some are defined in Flags.js > Used with utils.IsKeyPressed().
         * @return {boolean}
         */
        IsKeyPressed: function (vkey) { }, // (boolean)

        /**
         * See {@link https://docs.microsoft.com/en-us/windows/desktop/api/winnls/nf-winnls-lcmapstringa}.
         *
         * @param {string} text
         * @param {string} lcid
         * @param {number} flags
         * @return {string}
         */
        MapString: function (text, lcid, flags) { }, // (string)

        /**
         * Check if the supplied string matches the pattern.<br>
         * Using Microsoft MS-DOS wildcards match type. eg "*.txt", "abc?.tx?"
         *
         * @param {string} pattern
         * @param {string} str
         * @return {boolean}
         */
        PathWildcardMatch: function (pattern, str) { }, // (boolean)

        /**
         * Performance note: supply codepage argument if it is known, since codepage detection might take some time.
         *
         * @param {string} filename
         * @param {number=} [codepage=0] See Codepages.js. If codepage is 0, then automatic detection is performed.
         * @return {string}
         *
         * @example
         * let text = utils.ReadTextFile("E:\\some text file.txt");
         */
        ReadTextFile: function (filename, codepage) { }, // (string) [,codepage]

        /**
         * Note: this only returns up to 255 characters per value.
         *
         * @param {string} filename
         * @param {string} section
         * @param {string} key
         * @param {string=} [default_val]
         * @return {string}
         *
         * @example
         * let username = utils.ReadINI("e:\\my_file.ini", "Last.fm", "username");
         */
        ReadINI: function (filename, section, key, default_val) { }, // (string) [, default_val]

        /**
         * Displays an html dialog, rendered by IE engine.<br>
         * Utilizes the latest non-Edge IE that you have on your system.<br>
         * Dialog is modal (blocks input to the parent window while open).<br>
         *<br>
         * Html code must be IE compatible, meaning:<br>
         * - JavaScript features are limited by IE (see {@link https://www.w3schools.com/js/js_versions.asp}).<br>
         * - Objects passed to `data` are limited to standard JavaScript objects:<br>
         *   - No extensions from Spider Monkey Panel (e.g. no FbMetadbHandle or GdiBitmap).<br>
         *<br>
         * There are also additional limitations:<br>
         * - options.data may contain only the following types:<br>
         *   - Basic types: number, string, boolean, null, undefined.<br>
         *   - Objects as string: the only way to pass objects is to convert them to string and back with `JSON.stringify()` and `JSON.parse()`.<br>
         *   - Arrays: must be cast via `.toArray()` inside html. Each element has same type limitations as options.data.<br>
         *   - Functions: with maximum of 7 arguments. Each argument has same type limitations as options.data.
         *
         * @param {number} window_id {@link window.ID}
         * @param {string} code_or_path Html code or file path. File path must begin with `file://` prefix.
         * @param {object=} [options=undefined]
         * @param {number=} [options.width=250] Window width
         * @param {number=} [options.height=100] Window height
         * @param {number=} [options.x=0] Window horizontal position relative to desktop
         * @param {number=} [options.y=0] Window vertical position relative to desktop
         * @param {boolean=} [options.center=true] If true and if options.x and options.y are not set, will center window relative to fb2k position.
         * @param {boolean=} [options.context_menu=false] If true, will enable right-click context menu.
         * @param {boolean=} [options.resizable=false] If true, will allow to resize the window.
         * @param {boolean=} [options.selection=false] If true, will allow to select everything (label texts, buttons and etc).
         * @param {boolean=} [options.scroll=false] If true, will display scrollbars.
         * @param {*=} [options.data=undefined] Will be saved in `window.external.dialogArguments` and can be accessed from JavaScript executed inside HTML window.
         *                                      This data is read-only and should not be modified. Has type limitations (see above).
         *
         * @example <caption>Dialog from code</caption>
         * // See `samples/basic/HtmlDialogWithCheckbox.js`
         *
         * @example <caption>Dialog from file</caption>
         * utils.ShowHtmlDialog(window.ID, `file://${fb.ComponentPath}samples/basic/html/PopupWithCheckBox.html`);
         */
        ShowHtmlDialog: function (window_id, code_or_path, options) { },

        /**
         * @param {string} filename
         * @param {string} section
         * @param {string} key
         * @param {string} val
         * @return {boolean}
         *
         * @example
         * utils.WriteINI("e:\\my_file.ini", "Last.fm", "username", "Bob");
         */
        WriteINI: function (filename, section, key, val) { }, // (boolean)

        /**
         * Note: the parent folder must already exist.
         * Note2: the file is written with UTF8 encoding.
         *
         * @param {string} filename
         * @param {string} content
         * @param {boolean=} [write_bom=true]
         * @return {boolean}
         *
         * @example <caption>Default encoding</caption>
         * // write_bom missing but defaults to true, resulting file is UTF8-BOM
         * utils.WriteTextFile("z:\\1.txt", "test");
         *
         * @example <caption>UTF8 with BOM</caption>
         * utils.WriteTextFile("z:\\2.txt", "test", true);
         *
         * @example <caption>UTF8 without BOM</caption>
         * utils.WriteTextFile("z:\\3.txt", "test", false);
         */
        WriteTextFile: function (filename, content, write_bom) { }, //(boolean)
    };

    /**
     * Functions for working with the current SMP panel and accessing it's properties.
     *
     * @namespace
     */
    let window = {
        /**
         * Indicates which keys should be processed by the panel.<br>
         * See {@link https://docs.microsoft.com/en-us/windows/desktop/dlgbox/wm-getdlgcode} for more info.
         *
         * @return {number} See Flags.js > With window.DlgCode
         *
         * @example
         * window.DlgCode = DLGC_WANTALLKEYS;
         */
        DlgCode: undefined, // (uint) (read, write)

        /**
         * Required in multiple methods such as {@link fb.GetClipboardContents}, {@link utils.ColourPicker}, {@link utils.GetAlbumArtAsync},
         * {@link utils.InputBox}, {@link utils.LoadImageAsync} and etc.
         *
         * @type {number}
         * @readonly
         */
        ID: undefined, // (read) (uint)

        /**
         * You need this to determine which GetFontXXX and GetColourXXX methods to use, assuming you want to support both interfaces.<br>
         * <br>
         * 0 - if using Columns UI<br>
         * 1 - if using default UI.
         *
         * @type {number}
         * @readonly
         */
        InstanceType: undefined, // (uint)

        /**
         * Only useful within Panel Stack Splitter (Columns UI component)<br>
         * Depends on setting inside Spider Monkey Panel Configuration window. You generally use it to determine
         * whether or not to draw a background.
         *
         * @type {boolean}
         * @readonly
         */
        IsTransparent: undefined, // (boolean) (read)

        /**
         * @type {boolean}
         * @readonly
         */
        IsVisible: undefined, // (boolean) (read)

        /**
         * @type {number}
         * @readonly
         */
        Height: undefined, // (uint) (read)

        /**
         * {@link window.MaxHeight}, {@link window.MaxWidth}, {@link window.MinHeight} and {@link window.MinWidth} can be used to lock the panel size.<br>
         * Do not use if panels are contained within Panel Stack Splitter (Columns UI component).
         *
         * @type {number}
         */
        MaxHeight: undefined, // (uint) (read, write)

        /**
         * See {@link window.MaxHeight}.
         *
         * @type {number}
         */
        MaxWidth: undefined, // (uint) (read, write)

        /**
         * Maximum allowed memory usage for the component (in bytes).<br>
         * If the total memory usage exceeds this value, all panels will fail with OOM error.
         *
         * @type {number}
         * @readonly
         */
        MemoryLimit: undefined, // (uint) (read)

        /**
         * See {@link window.MaxHeight}.
         *
         * @type {number}
         */
        MinHeight: undefined, // (uint) (read, write)

        /**
         * See {@link window.MaxHeight}.
         *
         * @type {number}
         */
        MinWidth: undefined, // (uint) (read, write)

        /**
         * Returns the author set in {@link window.DefinePanel}.
         * If it isn't present, the GUID of the panel is returned.
         *
         * @type {string}
         * @readonly
         */
        Name: undefined, // (string) (read)

        /**
         * Memory usage of the current panel (in bytes).
         *
         * @type {number}
         * @readonly
         */
        PanelMemoryUsage: undefined, // (uint) (read)

        /**
         * Get associated tooltip object.
         *
         * @type {FbTooltip}
         * @readonly
         */
        Tooltip: undefined,

        /**
         * Total memory usage of all panels (in bytes).
         *
         * @type {number}
         * @readonly
         */
        TotalMemoryUsage: undefined, // (uint) (read)

        /**
         * @type {number}
         * @readonly
         */
        Width: undefined, // (uint) (read)

        /**
         * See {@link clearTimeout}.
         *
         * @param {number} timerID
         */
        ClearTimeout: function (timerID) { }, // (void)

        /**
         * See {@link clearInterval}.
         *
         * @param {number} timerID
         */
        ClearInterval: function (timerID) { }, // (void)

        /**
         * Setups the panel information and available features.<br>
         * Can be called only once, so it's better to define it
         * directly in the panel Configure menu.
         *
         * @param {string} name Displayed panel name
         * @param {object=} [options={}]
         * @param {string=} [options.author=''] Script author
         * @param {string=} [options.version=''] Script version
         * @param {object=} [options.features=undefined] Additional script features
         * @param {boolean=} [options.features.drag_n_drop=false] Indicates if drag_n_drop functionality should be enabled
         */
        DefinePanel: function (name, options) { }, // (void)

        /**
         * @return {MenuObject}
         *
         * @example
         * // See `samples/basic/MainMenuManager All-In-One.js`, `samples/basic/Menu Sample.js`
         */
        CreatePopupMenu: function () { }, // (MenuObject)

        /**
         * @param {string} class_id {@link https://docs.microsoft.com/en-us/windows/win32/controls/parts-and-states}
         * @return {ThemeManager}
         *
         * @example
         * // See `samples/basic/SimpleThemedButton.js`
         */
        CreateThemeManager: function (class_id) { }, // (ThemeManager)

        /**
         * Note: a single panel can have only a single tooltip object.
         * Creating a new tooltip will replace the previous one.
         *
         * Deprecated: use {@link fb.Tooltip} and {@link FbTooltip.SetFont} instead.
         *
         * @deprecated
         *
         * @param {string=} [font_name='Segoe UI']
         * @param {number=} [font_size_px=12]
         * @param {number=} [font_style=0] See Flags.js > FontStyle
         * @return {FbTooltip}
         */
        CreateTooltip: function (font_name, font_size_px, font_style) { }, // (FbTooltip) [font_name][, font_size_px][, font_style]

        /**
         * @param {number} type See Flags.js > Used in window.GetColourXXX()
         * @param {string=} client_guid See Flags.js > Used in GetColourCUI() as client_guid.
         * @return {number} returns black colour if the requested one is not available.
         */
        GetColourCUI: function (type, client_guid) { }, // (uint) [, client_guid]

        /**
         * @param {number} type
         * @return {number} returns black colour if the requested one is not available.
         */
        GetColourDUI: function (type) { }, // (uint)

        /**
         * Note: see the example in {@link window.GetFontDUI}.
         *
         * @param {number} type See Flags.js > Used in window.GetFontXXX()
         * @param {string=} client_guid See Flags.js > Used in GetFontCUI() as client_guid.
         * @return {?GdiFont} returns null if the requested font was not found.
         */
        GetFontCUI: function (type, client_guid) { }, // (GdiFont) [, client_guid]

        /**
         * @param {number} type See Flags.js > Used in window.GetFontXXX()
         * @return {?GdiFont} returns null if the requested font was not found.
         *
         * @example
         * // To avoid errors when trying to use the font or access its properties, you
         * // should use code something like this...
         * let font = window.GetFontDUI(0);
         * if (!font) {
         *    console.log("Unable to determine your default font. Using Segoe UI instead.");
         *    font = gdi.Font("Segoe UI", 12);
         * }
         */
        GetFontDUI: function (type) { }, // (GdiFont)

        /**
         * Get value of property.<br>
         * If property does not exist and default_val is not undefined and not null,
         * it will be created with the value of default_val.<br>
         * <br>
         * Note: leading and trailing whitespace are removed from property name.
         *
         * @param {string} name
         * @param {*=} default_val
         * @return {*}
         */
        GetProperty: function (name, default_val) { }, // (VARIANT) [, default_val]

        /**
         * This will trigger {@link module:callbacks~on_notify_data on_notify_data}(name, info) in other panels.<br>
         *
         * @param {string} name
         * @param {*} info
         */
        NotifyOthers: function (name, info) { }, // (void)

        /**
         * Reload panel.
         * @method
         */
        Reload: function () { }, // (void)

        /**
         * Performance note: don't force the repaint unless it's really necessary -
         * repaint calls might be grouped up when *not forced* which will turn them into a single repaint call,
         * thus reducing the amount of {@link module:callbacks~on_paint on_paint} calls.
         *
         * @param {boolean=} [force=false] If true, will repaint immediately, otherwise a repaint task will be *scheduled*.
         */
        Repaint: function (force) { }, // (void) [force]

        /**
         * Repaints a part of the screen.<br>
         * Use this instead of {@link window.Repaint} on frequently updated areas
         * such as time, bitrate, seekbar, etc.<br>
         * <br>
         * Performance note: see Performance note in {@link window.Repaint}.
         *
         * @param {number} x
         * @param {number} y
         * @param {number} w
         * @param {number} h
         * @param {boolean=} [force=false] If true, will repaint immediately, otherwise a repaint task will be *scheduled*.
         */
        RepaintRect: function (x, y, w, h, force) { }, // (void) [force]

        /**
         * This would usually be used inside the {@link module:callbacks~on_mouse_move on_mouse_move} callback.<br>
         * Use -1 if you want to hide the cursor.
         *
         * @param {number} id See Flags.js > Used in window.SetCursor()
         */
        SetCursor: function (id) { }, // (void)

        /**
         * See {@link setInterval}.
         *
         * @param {function()} func
         * @param {number} delay
         *
         * @return {number}
         */
        SetInterval: function (func, delay) { }, // (uint)

        /**
         * Set property value.<br>
         * Property will be removed, if val is undefined or null.<br>
         * <br>
         * Property values are saved per panel instance and are remembered between foobar2000 restarts.<br>
         * <br>
         * Note: leading and trailing whitespace are removed from property name.
         *
         * @param {string} name
         * @param {*=} val
         */
        SetProperty: function (name, val) { }, // (void)

        /**
         * See {@link setTimeout}.
         *
         * @param {function()} func
         * @param {number} delay
         *
         * @return {number}
         */
        SetTimeout: function (func, delay) { }, // (uint)

        /**
         * Show configuration window of current panel
         * @method
         */
        ShowConfigure: function () { }, // (void)

        /**
         * Show properties window of current panel
         * @method
         */
        ShowProperties: function () { }, // (void)
    };

    /**
     * @constructor
     * @hideconstructor
     */
    function FbMetadbHandle() {
        /**
         * @type {string}
         * @readonly
         *
         * @example
         * let handle = fb.GetFocusItem();
         * console.log(handle.Path); // D:\SomeSong.flac
         */
        this.Path = undefined; // (string) (read)

        /**
         * @type {string}
         * @readonly
         *
         * @example
         * console.log(handle.RawPath); // file://D:\SomeSong.flac
         */
        this.RawPath = undefined; // (string) (read)

        /**
         * @type {number}
         * @readonly
         */
        this.SubSong = undefined; // (uint) (read)

        /**
         * -1 if size is unavailable.
         *
         * @type {number}
         * @readonly
         */
        this.FileSize = undefined; // (LONGLONG) (read)

        /**
         * @type {float}
         * @readonly
         */
        this.Length = undefined; // (double) (read)

        /**
         * See {@link https://theqwertiest.github.io/foo_spider_monkey_panel/docs/guides/playback_stats}
         *
         * @param {number} playcount Use 0 to clear
         */
        this.SetPlayCount = function (playcount) { }; // (void)

        /**
         * See {@link https://theqwertiest.github.io/foo_spider_monkey_panel/docs/guides/playback_stats}
         *
         * @param {number} loved Use 0 to clear
         */
        this.SetLoved = function (loved) { }; // (void)

        /**
         * See {@link https://theqwertiest.github.io/foo_spider_monkey_panel/docs/guides/playback_stats}
         *
         * @param {string} first_played Use "" to clear
         */
        this.SetFirstPlayed = function (first_played) { }; // (void)

        /**
         * See {@link https://theqwertiest.github.io/foo_spider_monkey_panel/docs/guides/playback_stats}
         *
         * @param {string} last_played Use "" to clear
         */
        this.SetLastPlayed = function (last_played) { }; // (void)

        /**
         * See {@link https://theqwertiest.github.io/foo_spider_monkey_panel/docs/guides/playback_stats}
         *
         * @param {number} rating Use 0 to clear
         */
        this.SetRating = function (rating) { }; // (void)

        /**
         * See {@link https://theqwertiest.github.io/foo_spider_monkey_panel/docs/guides/playback_stats}
         *
         * @method
         */
        this.ClearStats = function () { }; // (void)

        /**
         * See {@link https://theqwertiest.github.io/foo_spider_monkey_panel/docs/guides/playback_stats}
         *
         * @method
         */
        this.RefreshStats = function () { }; // (void)

        /**
         * Compare two {@link FbMetadbHandle} instances, pointer only.<br>
         * If you want to compare them physically, use the {@link FbMetadbHandle#RawPath} property.
         *
         * @param {FbMetadbHandle} handle
         * @return {boolean}
         *
         * @example
         * handle.Compare(handle2);
         */
        this.Compare = function (handle) { }; // (boolean)

        /**
         * @return {?FbFileInfo} null if file info is not available.
         */
        this.GetFileInfo = function () { }; // (FbFileInfo)
    }

    /**
     * @constructor
     * @hideconstructor
     */
    function FbFileInfo() {
        /**
         * @type {number}
         * @readonly
         *
         * @example
         * let handle = fb.GetFocusItem();
         * let file_info = handle.GetFileInfo();
         * if (file_info) {
         *     console.log(file_info.MetaCount); // 11
         * }
         */
        this.MetaCount = undefined; // (read)

        /**
         * @type {number}
         * @readonly
         *
         * @example
         * console.log(file_info.InfoCount); // 9
         */
        this.InfoCount = undefined; // (read)

        /**
         * @param {string} name
         * @return {number} -1 if not found
         */
        this.InfoFind = function (name) { }; //

        /**
         * @param {number} idx
         * @return {string}
         */
        this.InfoName = function (idx) { }; //

        /**
         * @param {number} idx
         * @return {string}
         */
        this.InfoValue = function (idx) { }; //

        /**
         * @param {string} name
         * @return {number} -1 if not found
         */
        this.MetaFind = function (name) { }; //

        /**
         * Note: the case of the tag name returned can be different depending on tag type,
         * so using toLowerCase() or toUpperCase() on the result is recommended
         *
         * @param {number} idx
         * @return {string}
         *
         * @example
         * for (let i = 0; i < f.MetaCount; ++i) {
         *      console.log(file_info.MetaName(i).toUpperCase());
         * }
         */
        this.MetaName = function (idx) { }; //

        /**
         * @param {number} idx
         * @param {number} value_idx Used for iterating through multi-value tags.
         * @return {string}
         */
        this.MetaValue = function (idx, value_idx) { }; //

        /**
         * The number of values contained in a meta tag.
         *
         * @param {number} idx
         * @return {number}
         */
        this.MetaValueCount = function (idx) { }; //
    }

    /**
     * Handle list elements can be accessed with array accessor, e.g. handle_list[i]
     *
     * @constructor
     * @param {FbMetadbHandleList | FbMetadbHandle | Array<FbMetadbHandle> | null | undefined} [arg]
     */
    function FbMetadbHandleList(arg) {
        /**
         * @type {number}
         * @readonly
         *
         * @example
         * plman.GetPlaylistItems(plman.ActivePlaylist);
         * console.log(handle_list.Count);
         */
        this.Count = undefined; // (uint) (read)

        /**
         * @param {FbMetadbHandle} handle
         * @return {number}
         *
         * @example
         * handle_list.Add(fb.GetNowPlaying());
         */
        this.Add = function (handle) { }; // (uint)

        /**
         * @param {FbMetadbHandleList} handle_list
         *
         * @example
         * handle_list.AddRange(fb.GetLibraryItems());
         */
        this.AddRange = function (handle_list) { }; // (void)

        /**
         * Errors such as invalid path, corrupt image, target file type not supporting
         * embedded art, etc should all silently fail. A progress dialog will be shown for larger file
         * selections.<br>
         * Any existing artwork of the specified type will be overwritten - there is no need to remove it first.
         *
         * @param {FbMetadbHandleList} image_path path to an existing image
         * @param {number=} [art_id=0] See Flags.js > AlbumArtId
         *
         * @example
         * let handle_list = plman.GetPlaylistItems(plman.ActivePlaylist);
         * if (handle_list.Count > 0) {
         *    let img_path = 'C:\\path\\to\\image.jpg';
         *    handle_list.AttachImage(img_path, 0);
         * }
         *
         * @example
         * // since there is no handle method, do this for a single item
         * let handle_list = new FbMetadbHandleList(fb.GetFocusItem());
         * let img_path = "C:\\path\\to\\image.jpg";
         * handle_list.AttachImage(img_path, 0);
         */
        this.AttachImage = function (image_path, art_id) { }; //(void)

        /**
         * Faster than {@link FbMetadbHandleList#Find}.
         *
         * @param {FbMetadbHandle} handle Must be sorted with {@link FbMetadbHandleList#Sort}.
         * @return {number} -1 on failure.
         */
        this.BSearch = function (handle) { }; // (uint)

        /**
         * @return {float} total duration in seconds. For display purposes, consider using {@link utils.FormatDuration} on the result.
         */
        this.CalcTotalDuration = function () { }; // (double)

        /**
         * @return {number} total size in bytes. For display purposes, consider using utils.FormatFileSize() on the result.
         */
        this.CalcTotalSize = function () { }; // (LONGLONG)

        /**
         * @return {FbMetadbHandleList}
         *
         * @example
         * let handle_list2 = handle_list.Clone();
         */
        this.Clone = function () { }; // (FbMetadbHandleList)

        /**
         * Converts {@link FbMetadbHandleList} to an array of {@link FbMetadbHandle}.<br>
         * Use this instead of looping through {@link FbMetadbHandleList}, if the playlist is big
         * or if you need to loop multiple times.<br>
         *
         * @return {Array<FbMetadbHandle>}
         *
         * @example
         * let playlist_items_array = plman.GetPlaylistItems(plman.ActivePlaylist).Convert();
         * for (let i = 0; i < playlist_items_array.length; ++i) {
         *    // do something with playlist_items_array[i] which is your handle
         * }
         */
        this.Convert = function () { }; // (Array)

        /**
         * Performance note: if sorted with {@link FbMetadbHandleList#Sort}, use {@link FbMetadbHandleList#BSearch} instead.
         *
         * @param {FbMetadbHandle} handle
         * @return {number} index in the handle list on success, -1 if not found
         */
        this.Find = function (handle) { }; // (int)

        /**
         * See {@link fb.GetLibraryRelativePath}.<br>
         * <br>
         * This should be faster than looping a handle list manually and using the aforementioned method.
         *
         * @return {Array<string>}
         *
         * @example
         * let handle_list = fb.GetLibraryItems();
         * handle_list.OrderByRelativePath();
         * let relative_paths = handle_list.GetLibraryRelativePaths();
         */
        this.GetLibraryRelativePaths = function () { }; //(Array)

        /**
         * @param {number} index
         * @param {FbMetadbHandle} handle
         *
         * @example
         * // This inserts at the end of the handle list.
         * handle_list.Insert(handle_list.Count, fb.GetNowPlaying());
         */
        this.Insert = function (index, handle) { }; // (void)

        /**
         * @param {number} index
         * @param {FbMetadbHandleList} handle_list
         */
        this.InsertRange = function (index, handle_list) { }; // (void)

        /**
         * Note: sort with {@link FbMetadbHandleList#Sort} before using.
         *
         * @param {FbMetadbHandleList} handle_list Sorted handle list.
         *
         * @example
         * let one = plman.GetPlaylistItems(0);
         * one.Sort();
         *
         * let two = plman.GetPlaylistItems(1);
         * two.Sort();
         *
         * one.MakeDifference(two);
         * // "one" now only contains handles that were unique to "one".
         * // Anything that also existed in "two" will have been removed.
         */
        this.MakeDifference = function (handle_list) { }; // (void)

        /**
         * Note: sort with {@link FbMetadbHandleList#Sort} before using.
         *
         * @param {FbMetadbHandleList} handle_list Sorted handle list.
         *
         * @example
         * let one = plman.GetPlaylistItems(0);
         * one.Sort();
         *
         * let two = plman.GetPlaylistItems(1);
         * two.Sort();
         *
         * one.MakeIntersection(two);
         * // "one" now only contains handles that were in BOTH "one" AND "two"
         */
        this.MakeIntersection = function (handle_list) { }; // (void)

        /**
         * Note: sort with {@link FbMetadbHandleList#Sort} before using.
         *
         * @param {FbMetadbHandleList} handle_list Sorted handle list.
         *
         * @example
         * let one = plman.GetPlaylistItems(0);
         * one.Sort();
         *
         * let two = plman.GetPlaylistItems(1);
         * two.Sort();
         *
         * one.MakeUnion(two);
         * // "one" now contains all handles from "one" AND "two" with any duplicates removed
         */
        this.MakeUnion = function (handle_list) { }; // (void)

        /**
         * @param {FbTitleFormat} tfo An instance of FbTitleFormat.
         * @param {number} direction > 0 - ascending.
         *
         * @example
         * let handle_list = fb.GetLibraryItems();
         * let tfo = fb.TitleFormat("%album artist%|%date%|%album%|%discnumber%|%tracknumber%");
         * handle_list.OrderByFormat(tfo, 1);
         */
        this.OrderByFormat = function (tfo, direction) { }; // (void)

        /**
         * Note: this method should only be used on a handle list containing items that are monitored as part of the Media Library.
         *
         * @method
         */
        this.OrderByPath = function () { }; // (void)

        /** @method */
        this.OrderByRelativePath = function () { }; // (void)

        /**
         * See {@link https://theqwertiest.github.io/foo_spider_monkey_panel/docs/guides/playback_stats}
         *
         * @method
         */
        this.RefreshStats = function () { }; // (void)

        /**
         * @param {FbMetadbHandle} handle
         */
        this.Remove = function (handle) { }; // (void)

        /** @method */
        this.RemoveAll = function () { }; // (void)

        /**
         * Note: a progress dialog will be shown for larger file selections.
         *
         * @param {number=} [art_id=0] See Flags.js > AlbumArtId
         */
        this.RemoveAttachedImage = function (art_id) { }; // (void)

        /**
         * Removes all attached images.
         *
         * Note: a progress dialog will be shown for larger file selections.
         */
        this.RemoveAttachedImages = function () { }; // (void)

        /**
         * @param {number} idx
         *
         * @example
         * handle_list.RemoveById(0);
         */
        this.RemoveById = function (idx) { }; // (void)

        /**
         * @param {number} from
         * @param {number} num
         *
         * @example
         * handle_list.RemoveRange(10, 20);
         */
        this.RemoveRange = function (from, num) { }; // (void)

        /**
         * Remove duplicates and optimise for other handle list operations
         *
         * @method
         */
        this.Sort = function () { }; // (void)

        /**
         * Updated metadb tags with new values.
         *
         * @param {string} str JSON string, which contains an object (applies same values to every track)
         *                     or an array of objects (one object per track).
         *
         * @example
         * // assume we've selected one album
         * let handles = plman.GetPlaylistSelectedItems(plman.ActivePlaylist);
         *
         * let arr = [];
         * for (let i = 0; i < handles.Count; ++i) {
         *     // each element of the array must be an object of key names/values, indicated by the curly braces
         *     arr.push({
         *         'tracknumber' : i + 1, // independent values per track
         *         'totaltracks' : handles.Count,
         *         'album' : 'Greatest Hits', // a simple string for a single value
         *         'genre' : ['Rock', 'Hard Rock'], // we can use an array here for multiple value tags
         *         'bad_tag' : '' // blank values will clear any existing tags.
         *     });
         * }
         *
         * handles.UpdateFileInfoFromJSON(JSON.stringify(arr));
         */
        this.UpdateFileInfoFromJSON = function (str) { }; // (void)
    }

    /**
     * A Recycle Bin for playlists.
     *
     * @constructor
     * @hideconstructor
     */
    function FbPlaylistRecycler() {

        /**
         * @type {number}
         * @readonly
         */
        this.Count = undefined; // (uint) (read)

        /**
         * @param {number} index
         * @return {string}
         */
        this.GetName = function (index) { }; // (string) (read)

        /**
         * @param {number} index
         * @return {FbMetadbHandleList}
         */
        this.GetContent = function (index) { }; // (FbMetadbHandleList) (read)

        /**
         * @param {number} affectedItems array like [1, 3, 5]
         */
        this.Purge = function (affectedItems) { }; // (void)

        /**
         * @param {number} index
         */
        this.Restore = function (index) { }; // (void)
    }

    /**
     * @constructor
     * @hideconstructor
     *
     * @example
     * let playing_item_location = plman.GetPlayingItemLocation();
     * if (playing_item_location.IsValid) {
     *     console.log(playing_item_location.PlaylistIndex);
     *     console.log(playing_item_location.PlaylistItemIndex);
     * }
     */
    function FbPlayingItemLocation() {

        /**
         * False if foobar2000 isn't playing or if the playing track
    	 * has since been removed from the playlist it was on when playback was started.
         *
         * @type {boolean}
         * @readonly
         */
        this.IsValid = undefined; // (boolean) (read)

        /**
         * -1 if item is not in a playlist
         *
         * @type {number}
         * @readonly
         */
        this.PlaylistIndex = undefined; // (int) (read)

        /**
         * -1 if item is not in a playlist
         *
         * @type {number}
         * @readonly
         */
        this.PlaylistItemIndex = undefined; // (int) (read)
    }

    /**
     * @constructor
     * @hideconstructor
     */
    function FbPlaybackQueueItem() {

        /**
         * @type {FbMetadbHandle}
         * @readonly
         */
        this.Handle = undefined; // (FbMetadbHandle) (read)

        /**
         * -1 if item is not in a playlist
         *
         * @type {number}
         * @readonly
         */
        this.PlaylistIndex = undefined; // (int) (read)

        /**
         * -1 if item is not in a playlist
         *
         * @type {number}
         * @readonly
         */
        this.PlaylistItemIndex = undefined; // (int) (read)
    }

    /**
     * @constructor
     * @param {string} name
     *
     * @example
     * let test = new FbProfiler('test');
     * // do something time consuming
     * console.log(test.Time); // Outputs bare time in ms like "789"
     * test.Print(); // Outputs component name/version/assigned name like "Spider Monkey Panel v1.0.0: profiler (test): 789 ms"
     */
    function FbProfiler(name) {

        /**
         * @type {number}
         * @readonly
         */
        this.Time = undefined; // (uint) // milliseconds

        /** @method */
        this.Reset = function () { }; // (void)

        /**
         * @param {string=} [additionalMsg=''] string that will be prepended to the measured time
         * @param {boolean=} [printComponentInfo=true]
         *
         * @example
         * let test = new FbProfiler('Group #1');
         * // Do smth #1
         * test.Print('\nTask #1:', false);
         * // Do smth #2
         * test.Print('\nTask #2:', false);
         * // Do smth
         * test.Print();
         * // Output:
         * // profiler (Group #1):
         * // Task #1: 789 ms"
         * // profiler (Group #1):
         * // Task #2: 1530 ms"
         * // Spider Monkey Panel v1.0.0: profiler (Group #1): 3541 ms"
         */
        this.Print = function (additionalMsg, printComponentInfo) { }; // (void)
    }

    /**
     * Performance note: if you use the same query frequently,
     * try caching FbTitleFormat object (by storing it somewhere),
     * instead of creating it every time.
     *
     * @constructor
     * @param {string} expression
     */
    function FbTitleFormat(expression) {
        /**
         * Always use Eval when you want dynamic info such as %playback_time%, %bitrate% etc.<br>
         * {@link FbTitleFormat#EvalWithMetadb}(fb.GetNowplaying()) will not give the results you want.
         *
         * @param {boolean=} [force=false] If true, you can process text that doesn't contain
         *     title formatting even when foobar2000 isn't playing. When playing, you
         *     should always get a result.
         * @return {string}
         *
         * @example
         * let tfo = fb.TitleFormat("%artist%");
         * console.log(tfo.Eval());
         */
        this.Eval = function (force) { }; // [force]

        /**
         * @param {FbMetadbHandle} handle
         * @return {string}
         *
         * @example
         * let tfo = fb.TitleFormat("%artist%");
         * console.log(tfo.EvalWithMetadb(fb.GetFocusItem()));
         */
        this.EvalWithMetadb = function (handle) { }; //

        /**
         * @param {FbMetadbHandleList} handle_list
         * @return {Array<string>}
         *
         * @example
         * let tfo = fb.TitleFormat("%artist%");
         * let handle_list = fb.GetLibraryItems();
         * let artists = tfo.EvalWithMetadbs(handle_list);
         * console.log(handle_list.Count === artists.length); // should always be true!
         */
        this.EvalWithMetadbs = function (handle_list) { }; //(Array)
    }

    /**
     * @constructor
     * @hideconstructor
     */
    function FbTooltip() {
        /**
         * Note: this also updates text on the active tooltip
         * i.e. there is no need to manually cycle Deactivate()/Activate()
         * to update text.
         *
         * @type {string}
         *
         * @example
         * let tooltip = window.Tooltip;
         * tooltip.Text = "Whoop";
         */
        this.Text = undefined; // (string) (read, write)

        /** @type {boolean} */
        this.TrackActivate = undefined; // (boolean) (write)

        /**
         * Note: only do this when text has changed, otherwise it will flicker.
         *
         * @method
         *
         * @example
         * let text = "...";
         * if (tooltip.Text != text) {
         *    tooltip.Text = text;
         *    tooltip.Activate();
         * }
         */
        this.Activate = function () { }; // (void)

        /** @method */
        this.Deactivate = function () { }; // (void)

        /**
         * @param {number} type
         * @return {number}
         */
        this.GetDelayTime = function (type) { }; // (uint)

        /**
         * @param {number} type See Flags.js > Used in {@link FbTooltip#GetDelayTime} and {@link FbTooltip#SetDelayTime}
         * @param {number} time
         */
        this.SetDelayTime = function (type, time) { }; // (void)

        /**
         * @param {string} font_name
         * @param {number=} [font_size_px=12]
         * @param {number=} [font_style=0] See Flags.js > FontStyle
         */
        this.SetFont = function (font_name, font_size_px, font_style) { };

        /**
         * Use if you want multi-line tooltips.<br>
         * Use \n as a new line separator.
         *
         * @param {number} width
         *
         * @example
         * tooltip.SetMaxWidth(800);
         * tooltip.Text = "Line1\nLine2";
         */
        this.SetMaxWidth = function (width) { }; // (void)

        /**
         * Note: check that x, y positions have changed from the last invocation, otherwise it will flicker.<br>
         * Note 2: ensure that the tooltip does not overlap the mouse pointer, otherwise it will glitch out.
         *
         * @param {number} x
         * @param {number} y
         */
        this.TrackPosition = function (x, y) { }; // (void)
    }

    /**
     * This is typically used to update the selection used by the default UI artwork panel
     * or any other panel that makes use of the preferences under
     * File > Preferences > Display > Selection viewers. Use in conjunction with the {@link module:callbacks~on_focus on_focus}
     * callback.
     *
     * @constructor
     * @hideconstructor
     *
     * @example <caption>For playlist viewers</caption>
     * let selection_holder = fb.AcquireUiSelectionHolder();
     * selection_holder.SetPlaylistSelectionTracking();
     *
     * function on_focus(is_focused) {
     *     if (is_focused) { // Updates the selection when panel regains focus
     *         selection_holder.SetPlaylistSelectionTracking();
     *     }
     * }
     *
     * @example <caption>For library viewers</caption>
     * let selection_holder = fb.AcquireUiSelectionHolder();
     * let handle_list = null;
     *
     * function on_mouse_lbtn_up(x, y) { // Presumably going to select something here...
     *    handle_list = ...;
     *    selection_holder.SetSelection(handle_list);
     * }
     *
     * function on_focus(is_focused) {
     *    if (is_focused) { // Updates the selection when panel regains focus
     *        if (handle_list && handle_list.Count)
     *            selection_holder.SetSelection(handle_list);
     *    }
     * }
     */
    function FbUiSelectionHolder() {

        /**
         * Sets the selected items.
         *
         * @param {FbMetadbHandleList} handle_list
         */
        this.SetSelection = function (handle_list) { }; // (void)

        /**
         * Sets selected items to playlist selection and enables tracking.<br>
         * When the playlist selection changes, the stored selection is automatically
         * updated. Tracking ends when a set method is called on any ui_selection_holder
         * or when the last reference to this ui_selection_holder is released.
         */
        this.SetPlaylistSelectionTracking = function () { }; // (void)

        /**
         * Sets selected items to playlist contents and enables tracking.<br>
         * When the playlist selection changes, the stored selection is automatically
         * updated. Tracking ends when a set method is called on any ui_selection_holder
         * or when the last reference to this ui_selection_holder is released.
         */
        this.SetPlaylistTracking = function () { }; // (void)
    }

    /**
     * @constructor
     * @param {GdiBitmap} arg
     */
    function GdiBitmap(arg) {

        /**
         * @type {number}
         * @readonly
         */
        this.Height = undefined;// (uint) (read)

        /**
         * @type {number}
         * @readonly
         */
        this.Width = undefined;// (uint) (read)

        /**
         * @param {number} alpha Valid values 0-255.
         * @return {GdiBitmap}
         */
        this.ApplyAlpha = function (alpha) { }; // (GdiBitmap)

        /**
         * Changes will be saved in the current bitmap.
         *
         * @param {GdiBitmap} img
         *
         * @example <caption>Blur image<caption>
         * // See `samples/basic/Apply Mask.js`
         */
        this.ApplyMask = function (img) { }; // (boolean)

        /**
         * @param {number} x
         * @param {number} y
         * @param {number} w
         * @param {number} h
         * @return {GdiBitmap}
         */
        this.Clone = function (x, y, w, h) { }; // (GdiBitmap)

        /**
         * Create a DDB bitmap from GdiBitmap, which is used in {@link GdiGraphics#GdiDrawBitmap}
         *
         * @return {GdiRawBitmap}
         */
        this.CreateRawBitmap = function () { }; // (GdiRawBitmap)

        /**
         * @param {number} max_count
         * @return {Array<number>}
         */
        this.GetColourScheme = function (max_count) { }; // (Array)

        /**
         * Returns a JSON array in string form so you need to use JSON.parse() on the result.<br>
         * Each entry in the array is an object which contains colour and frequency values.<br>
         * Uses a different method for calculating colours than {@link GdiBitmap#GetColourScheme}.<br>
         * Image is automatically resized during processing for performance reasons so there's no
         * need to resize before calling the method.
         *
         * @param {number} max_count
         * @return {string}
         *
         * @example
         * // See docs\Helpers.js for "toRGB" function.
         * img = ... // use utils.GetAlbumArtV2 / gdi.Image / etc
         * colours = JSON.parse(img.GetColourSchemeJSON(5));
         * console.log(colours[0].col); // -4194304
         * console.log(colours[0].freq); // 0.34
         * console.log(toRGB(colours[0].col)); // [192, 0, 0]
         */
        this.GetColourSchemeJSON = function (max_count) { }; // (string)

        /**
         * Note: don't forget to use {@link GdiBitmap#ReleaseGraphics} after work on GdiGraphics is done!
         *
         * @return {GdiGraphics}
         */
        this.GetGraphics = function () { };

        /**
         * Inverts the colours in a bitmap, to create a negative image.
         * i.e. White becomes black, black becomes white, etc.
         * @return {GdiBitmap}
         */
        this.InvertColours = function () { }; // (GdiBitmap)

        /**
         * @param {GdiGraphics} gr
         */
        this.ReleaseGraphics = function (gr) { }; // (GdiGraphics)

        /**
         * @param {number} w
         * @param {number} h
         * @param {number=} [mode=0] See Flags.js > InterpolationMode
         * @return {GdiBitmap}
         */
        this.Resize = function (w, h, mode) { }; // (GdiBitmap) [, mode]

        /**
         * Changes will be saved in the current bitmap.
         *
         * @param {number} mode See Flags.js > RotateFlipType
         */
        this.RotateFlip = function (mode) { }; // (void)

        /**
         * @param {string} path Full path including file extension. The parent folder must already exist.
         * @param {string=} [format='image/png']
         *      "image/png"<br>
         *      "image/bmp"<br>
         *      "image/jpeg"<br>
         *      "image/gif"<br>
         *      "image/tiff"
         * @return {boolean}
         *
         * @example
         * let img = utils.GetAlbumArtEmbedded(fb.GetFocusItem().RawPath, 0);
         * if (img) {
         *     img.SaveAs("D:\\export.jpg", "image/jpeg");
         * }
         */
        this.SaveAs = function (path, format) { }; // (boolean) [, format]

        /**
         * Changes will be saved in the current bitmap.
         *
         * @param {number} radius Valid values 2-254.
         *
         * @example <caption>Blur image<caption>
         * // `samples/basic/StackBlur (image).js`
         *
         * @example <caption>Blur text<caption>
         * // `samples/basic/StackBlur (text).js`
         */
        this.StackBlur = function (radius) { }; // (void)
    }

    /**
     * Constructor may fail if font is not present.<br>
     *
     * Performance note: try caching and reusing `GdiFont` objects,
     * since the maximum amount of such objects is hard-limited by Windows.
     * `GdiFont` creation will fail after reaching this limit.
     *
     * @constructor
     * @param {string} name
     * @param {number} size_px See Helper.js > Point2Pixel function for conversions
     * @param {number=} [style=0] See Flags.js > FontStyle
     */
    function GdiFont(name, size_px, style) {
        /**
         * @type {number}
         * @readonly
         *
         * @example
         * console.log(my_font.Height); // 15
         */
        this.Height = undefined;//    (uint)(read)

        /**
         * @type {string}
         * @readonly
         *
         * @example
         * console.log(my_font.Name); // Segoe UI
         */
        this.Name = undefined;//    (string)(read)

        /**
         * @type {float}
         * @readonly
         *
         * @example
         * console.log(my_font.Size); // 12
         */
        this.Size = undefined;//    (float)(read)

        /**
         * See Flags.js > FontStyle for value interpretation.
         *
         * @type {number}
         * @readonly
         *
         * @example
         * console.log(my_font.Style);
         */
        this.Style = undefined;//    (uint)(read)
    }

    /**
     * Typically used inside `on_paint`.<br>
     *
     * Note: there are many different ways to get colours:
     * window.GetColourDUI/window.GetColourCUI,
     * RGB function from Helpers.js, utils.ColourPicker and
     * etc.
     *
     * @constructor
     * @hideconstructor
     */
    function GdiGraphics() {
        /**
         * Calculates text height for {@link GdiGraphics#GdiDrawText}.<br>
         * Note: this will only calculate the text height of one line.
         *
         * @param {string} str
         * @param {GdiFont} font
         * @return {number}
         */
        this.CalcTextHeight = function (str, font) { }; // (uint)

        /**
         * Calculates text width for {@link GdiGraphics#GdiDrawText}.
         *
         * @param {string} str
         * @param {GdiFont} font
         * @return {number}
         */
        this.CalcTextWidth = function (str, font) { }; // (uint)

        /**
         * @param {number} x
         * @param {number} y
         * @param {number} w
         * @param {number} h
         * @param {number} line_width
         * @param {number} colour
         */
        this.DrawEllipse = function (x, y, w, h, line_width, colour) { }; // (void)

        /**
         * @param {GdiBitmap} img
         * @param {number} dstX
         * @param {number} dstY
         * @param {number} dstW
         * @param {number} dstH
         * @param {number} srcX
         * @param {number} srcY
         * @param {number} srcW
         * @param {number} srcH
         * @param {float=} [angle=0]
         * @param {number=} [alpha=255] Valid values 0-255.
         */
        this.DrawImage = function (img, dstX, dstY, dstW, dstH, srcX, srcY, srcW, srcH, angle, alpha) { }; // (void) [, angle][, alpha]

        /**
         * @param {number} x1
         * @param {number} y1
         * @param {number} x2
         * @param {number} y2
         * @param {number} line_width
         * @param {number} colour
         */
        this.DrawLine = function (x1, y1, x2, y2, line_width, colour) { }; // (void)

        /**
         * @param {number} colour
         * @param {number} line_width
         * @param {Array<Array<number>>} points
         */
        this.DrawPolygon = function (colour, line_width, points) { }; // (void)

        /**
         * Should be only used when {@link GdiGraphics#GdiDrawText} is not applicable.
         *
         * @param {string} str
         * @param {GdiFont} font
         * @param {number} colour
         * @param {number} x
         * @param {number} y
         * @param {number} w
         * @param {number} h
         * @param {number=} [flags=0] See Flags.js > StringFormatFlags
         */
        this.DrawString = function (str, font, colour, x, y, w, h, flags) { }; // (void) [, flags]

        /**
         * @param {number} x
         * @param {number} y
         * @param {number} w
         * @param {number} h
         * @param {number} line_width
         * @param {number} colour
         */
        this.DrawRect = function (x, y, w, h, line_width, colour) { }; // (void)

        /**
         * @param {number} x
         * @param {number} y
         * @param {number} w
         * @param {number} h
         * @param {number} arc_width
         * @param {number} arc_height
         * @param {number} line_width
         * @param {number} colour
         */
        this.DrawRoundRect = function (x, y, w, h, arc_width, arc_height, line_width, colour) { }; // (void)

        /**
         * @param {string} str
         * @param {GdiFont} font
         * @param {number} max_width
         * @return {Array<Array>}
         *    index | meaning <br>
         *    [0] text line 1 <br>
         *    [1] width of text line 1 (in pixel) <br>
         *    [2] text line 2 <br>
         *    [3] width of text line 2 (in pixel) <br>
         *    ... <br>
         *    [2n + 2] text line n <br>
         *    [2n + 3] width of text line n (px)
         */
        this.EstimateLineWrap = function (str, font, max_width) { }; // (Array)

        /**
         * @param {number} x
         * @param {number} y
         * @param {number} w
         * @param {number} h
         * @param {number} colour
         */
        this.FillEllipse = function (x, y, w, h, colour) { }; // (void)

        /**
         * Note: this may appear buggy depending on rectangle size. The easiest fix is
         * to adjust the "angle" by a degree or two.
         *
         * @param {number} x
         * @param {number} y
         * @param {number} w
         * @param {number} h
         * @param {float} angle
         * @param {number} colour1
         * @param {number} colour2
         * @param {float} [focus=1.0] Specify where the centred colour will be at its highest intensity. Valid values between 0 and 1.
         */
        this.FillGradRect = function (x, y, w, h, angle, colour1, colour2, focus) { }; // (void) [, focus]

        /**
         * @param {number} colour
         * @param {number} fillmode 0 alternate, 1 winding.
         * @param {Array<Array<number>>} points
         */
        this.FillPolygon = function (colour, fillmode, points) { }; // (void)

        /**
         * @param {number} x
         * @param {number} y
         * @param {number} w
         * @param {number} h
         * @param {number} arc_width
         * @param {number} arc_height
         * @param {number} colour
         */
        this.FillRoundRect = function (x, y, w, h, arc_width, arc_height, colour) { }; // (void)

        /**
         * @param {number} x
         * @param {number} y
         * @param {number} w
         * @param {number} h
         * @param {number} colour
         */
        this.FillSolidRect = function (x, y, w, h, colour) { }; // (void)

        /**
         * @param {GdiRawBitmap} img
         * @param {number} dstX
         * @param {number} dstY
         * @param {number} dstW
         * @param {number} dstH
         * @param {number} srcX
         * @param {number} srcY
         * @param {number} srcW
         * @param {number} srcH
         * @param {number=} [alpha=255] Valid values 0-255.
         */
        this.GdiAlphaBlend = function (img, dstX, dstY, dstW, dstH, srcX, srcY, srcW, srcH, alpha) { }; // (void) [, alpha]

        /**
         * Always faster than {@link GdiGraphics#DrawImage}, does not support alpha channel.
         *
         * @param {GdiRawBitmap} img
         * @param {number} dstX
         * @param {number} dstY
         * @param {number} dstW
         * @param {number} dstH
         * @param {number} srcX
         * @param {number} srcY
         * @param {number} srcW
         * @param {number} srcH
         */
        this.GdiDrawBitmap = function (img, dstX, dstY, dstW, dstH, srcX, srcY, srcW, srcH) { }; // (void)

        /**
         * Provides faster and better rendering than {@link GdiGraphics#DrawString}.<br>
         * <br>
         * Do not use this to draw text on transparent background or
         * with GdiGraphics other than the one passed in {@link module:callbacks~on_paint on_paint} callback:
         * this will result in visual artifacts caused by ClearType hinting.<br>
         * Use {@link GdiGraphics#DrawString} instead in such cases.<br>
         * <br>
         * To calculate text dimensions use {@link GdiGraphics#CalcTextHeight}, {@link GdiGraphics#CalcTextWidth} or DT_CALCRECT flag.<br>
         * <br>
         * Note: uses special rules for `&` character by default, which consumes the `&` and causes the next character to be underscored.
         * This behaviour can be changed (or disabled) via `format` parameter.
         *
         * @param {string} str
         * @param {GdiFont} font
         * @param {number} colour
         * @param {number} x
         * @param {number} y
         * @param {number} w
         * @param {number} h
         * @param {number=} [format=0] See Flags.js > DT_*
         * @return {Array<number>}
         *     index | meaning <br>
         *     [0] left   (DT_CALCRECT) <br>
         *     [1] top    (DT_CALCRECT) <br>
         *     [2] right  (DT_CALCRECT) <br>
         *     [3] bottom (DT_CALCRECT) <br>
         *     [4] characters drawn
         */
        this.GdiDrawText = function (str, font, colour, x, y, w, h, format) { }; // (Array) [, format]

        /**
         * Calculates text dimensions for {@link GdiGraphics#DrawString}.
         *
         * @param {string} str
         * @param {GdiFont} font
         * @param {number} x
         * @param {number} y
         * @param {number} w
         * @param {number} h
         * @param {number=} [flags=0] See Flags.js > StringFormatFlags
         * @return {MeasureStringInfo}
         */
        this.MeasureString = function (str, font, x, y, w, h, flags) { }; // (MeasureStringInfo) [, flags]

        /**
         * @constructor
         * @hideconstructor
         *
         * @example
         * include(`${fb.ComponentPath}docs\\Flags.js`);
         * include(`${fb.ComponentPath}docs\\Helpers.js`);
         *
         * let sf = StringFormat(StringAlignment.Near, StringAlignment.Near);
         * let text = utils.ReadTextFile("z:\\info.txt");
         * let font = window.GetFontDUI(0);
         *
         * function on_paint(gr) {
         *     gr.DrawString(text, font, RGB(255, 0, 0), 0, 0, window.Width, window.Height, sf);
         *     let temp = gr.MeasureString(text, font, 0, 0, window.Width, 10000, sf);
         *     // If we want to calculate height, we must set the height to be far larger than what
         *     // the text could possibly be.
         *
         *     console.log(temp.Height); // 2761.2421875 // far larger than my panel height!
         *     console.log(temp.Chars); // 7967
         * }
         */
        function MeasureStringInfo() {

            /**
             * @type {number}
             * @readonly
             */
            this.Chars = undefined; // (uint) (read)

            /**
             * @type {float}
             * @readonly
             */
            this.Height = undefined; // (float) (read)

            /**
             * @type {number}
             * @readonly
             */
            this.Lines = undefined; // (uint) (read)

            /**
             * @type {float}
             * @readonly
             */
            this.X = undefined; // (float) (read)

            /**
             * @type {float}
             * @readonly
             */
            this.Y = undefined; // (float) (read)

            /**
             * @type {float}
             * @readonly
             */
            this.Width = undefined; // (float) (read)
        }

        /**
         * @param {number=} [mode=0] See Flags.js > InterpolationMode
         */
        this.SetInterpolationMode = function (mode) { }; // (void)

        /**
         * @param {number=} [mode=0] See Flags.js > SmoothingMode
         */
        this.SetSmoothingMode = function (mode) { }; // (void)

        /**
         * @param {number=} [mode=0] See Flags.js > TextRenderingHint
         */
        this.SetTextRenderingHint = function (mode) { }; // (void)
    }

    /**
     * @constructor
     * @hideconstructor
     */
    function GdiRawBitmap() {

        /**
         * @type {number}
         * @readonly
         */
        this.Width = undefined; // (uint) (read)

        /**
         * @type {number}
         * @readonly
         */
        this.Height = undefined; // (uint) (read)
    }

    /**
     * @constructor
     * @hideconstructor
     */
    function DropTargetAction() {

        /** @type {number} */
        this.Base = undefined; // (write)

        /**
         * See {@link https://docs.microsoft.com/en-us/windows/win32/com/dropeffect-constants}
         *
         * @type {number}
         */
        this.Effect = undefined; //(read, write)

        /**
         * Active playlist.<br>
         * -1 by default.<br>
         * <br>
         * Note: property is write-only.
         *
         * @type {number}
         */
        this.Playlist = undefined; // (write)

        /**
         * The tooltip text that is displayed during dragging.<br>
         * If the property is not modified, then default tooltip text will be used.
         * <br>
         * Note: property is write-only.
         *
         * @type {string}
         */
        this.Text = undefined; // (write)

        /**
         * Note: property is write-only.
         *
         * @type {boolean}
         */
        this.ToSelect = undefined; // (boolean) (write)
    }

    /**
     * @constructor
     * @hideconstructor
     */
    function ContextMenuManager() {
        /**
         * @param {MenuObject} menu_obj
         * @param {number} base_id
         * @param {number=} [max_id=-1]
         */
        this.BuildMenu = function (menu_obj, base_id, max_id) { }; // (void)

        /**
         * @param {number} id
         * @return {boolean}
         */
        this.ExecuteByID = function (id) { }; // (boolean)

        /**
         * Initializes context menu by supplied tracks.
         *
         * @param {FbMetadbHandleList} handle_list
         */
        this.InitContext = function (handle_list) { }; // (void)

        /**
         * Shows playlist specific options that aren't available when passing a
         * handle list to {@link ContextMenuManager#InitContext}.
         */
        this.InitContextPlaylist = function () { }; // (void)

        /**
         * Initializes context menu by currently played track.
         *
         * @method
         */
        this.InitNowPlaying = function () { }; // (void)
    }

    /**
     * @constructor
     * @hideconstructor
     */
    function MainMenuManager() {
        /**
         * @param {MenuObject} menu_obj
         * @param {number} base_id
         * @param {number} count
         */
        this.BuildMenu = function (menu_obj, base_id, count) { }; // (void)

        /**
         * @param {number} id
         * @return {boolean}
         */
        this.ExecuteByID = function (id) { }; // (boolean)

        /**
         * @param {string} root_name Must be one of the following: 'file', 'view', 'edit', 'playback', 'library', 'help'
         */
        this.Init = function (root_name) { }; // (void)
    }

    /**
     * @constructor
     * @hideconstructor
     */
    function MenuObject() {

        /**
         * @param {number} flags See Flags.js > Used in AppendMenuItem()
         * @param {number} item_id Integer greater than 0. Each menu item needs a unique id.
         * @param {string} text
         */
        this.AppendMenuItem = function (flags, item_id, text) { }; // (void)

        /** @method */
        this.AppendMenuSeparator = function () { }; // (void)

        /**
         * @param {MenuObject} parent_menu
         * @param {number} flags See Flags.js > Used in AppendMenuItem()
         * @param {string} text
         */
        this.AppendTo = function (parent_menu, flags, text) { }; // (void)

        /**
         * @param {number} item_id
         * @param {boolean} check
         */
        this.CheckMenuItem = function (item_id, check) { }; // (void)

        /**
         * @param {number} first_item_id
         * @param {number} last_item_id
         * @param {number} selected_item_id
         */
        this.CheckMenuRadioItem = function (first_item_id, last_item_id, selected_item_id) { }; // (void)

        /**
         * @param {number} x
         * @param {number} y
         * @param {number=} [flags=0] See Flags.js > Used in TrackPopupMenu().
         * @return {number}
         */
        this.TrackPopupMenu = function (x, y, flags) { }; // (uint) [, flags]
    }

    /**
     * @constructor
     * @hideconstructor
     */
    function ThemeManager() {
        /**
         * @param {GdiGraphics} gr
         * @param {number} x
         * @param {number} y
         * @param {number} w
         * @param {number} h
         * @param {number=} [clip_x=0]
         * @param {number=} [clip_y=0]
         * @param {number=} [clip_w=0]
         * @param {number=} [clip_h=0]
         */
        this.DrawThemeBackground = function (gr, x, y, w, h, clip_x, clip_y, clip_w, clip_h) { }; // (void) [, clip_x][, clip_y][, clip_w][, clip_h]

        /**
         * @param {number} partid
         * @return {boolean}
         */
        this.IsThemePartDefined = function (partid) { }; // (boolean)

        /**
         * See {@link https://docs.microsoft.com/en-us/windows/win32/controls/parts-and-states}
         *
         * @param {number} partid
         * @param {number=} [stateid=0]
         */
        this.SetPartAndStateID = function (partid, stateid) { }; // (void)
    }