More featured menu

Alright here’s my code:

/*
 * This file is part of budgie-desktop
 * 
 * Copyright © 2015-2019 Budgie Desktop Developers
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 */

public class BudgieMenu : Budgie.Plugin, Peas.ExtensionBase
{
    public Budgie.Applet get_panel_widget(string uuid)
    {
        return new BudgieMenuApplet(uuid);
    }
}

[GtkTemplate (ui = "/com/solus-project/budgie-menu/settings.ui")]
public class BudgieMenuSettings : Gtk.Grid
{

    [GtkChild]
    private Gtk.Switch? switch_menu_label;

    [GtkChild]
    private Gtk.Switch? switch_menu_compact;

    [GtkChild]
    private Gtk.Switch? switch_menu_headers;

    [GtkChild]
    private Gtk.Switch? switch_menu_categories_hover;

    [GtkChild]
    private Gtk.Entry? entry_label;

    [GtkChild]
    private Gtk.Entry? entry_icon_pick;

    [GtkChild]
    private Gtk.Button? button_icon_pick;

    private Settings? settings;

    public BudgieMenuSettings(Settings? settings)
    {
        this.settings = settings;
        settings.bind("enable-menu-label", switch_menu_label, "active", SettingsBindFlags.DEFAULT);
        settings.bind("menu-compact", switch_menu_compact, "active", SettingsBindFlags.DEFAULT);
        settings.bind("menu-headers", switch_menu_headers, "active", SettingsBindFlags.DEFAULT);
        settings.bind("menu-categories-hover", switch_menu_categories_hover, "active", SettingsBindFlags.DEFAULT);
        settings.bind("menu-label", entry_label, "text", SettingsBindFlags.DEFAULT);
        settings.bind("menu-icon", entry_icon_pick, "text", SettingsBindFlags.DEFAULT);

        this.button_icon_pick.clicked.connect(on_pick_click);
    }

    /**
     * Handle the icon picker
     */
    void on_pick_click()
    {
        IconChooser chooser = new IconChooser(this.get_toplevel() as Gtk.Window);
        string? response = chooser.run();
        chooser.destroy();
        if (response != null) {
            this.entry_icon_pick.set_text(response);
        }
    }
}

public class BudgieMenuApplet : Budgie.Applet
{

    protected Gtk.ToggleButton widget;
    protected BudgieMenuWindow? popover;
    protected Settings settings;
    Gtk.Image img;
    Gtk.Label label;
    Budgie.PanelPosition panel_position = Budgie.PanelPosition.BOTTOM;
    int pixel_size = 32;

    private unowned Budgie.PopoverManager? manager = null;

    public string uuid { public set ; public get; }

    public override Gtk.Widget? get_settings_ui()
    {
        return new BudgieMenuSettings(this.get_applet_settings(uuid));
    }

    public override bool supports_settings()
    {
        return true;
    }

    public BudgieMenuApplet(string uuid)
    {
        Object(uuid: uuid);

        settings_schema = "com.solus-project.budgie-menu";
        settings_prefix = "/com/solus-project/budgie-panel/instance/budgie-menu";

        settings = this.get_applet_settings(uuid);

        settings.changed.connect(on_settings_changed);

        widget = new Gtk.ToggleButton();
        widget.relief = Gtk.ReliefStyle.NONE;
        img = new Gtk.Image.from_icon_name("view-grid-symbolic", Gtk.IconSize.INVALID);
        img.pixel_size = pixel_size;
        img.no_show_all = true;

        var layout = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 0);
        layout.pack_start(img, true, true, 0);
        label = new Gtk.Label("");
        label.halign = Gtk.Align.START;
        layout.pack_start(label, true, true, 3);

        widget.add(layout);

        // Better styling to fit in with the budgie-panel
        var st = widget.get_style_context();
        st.add_class("budgie-menu-launcher");
        st.add_class("panel-button");
        popover = new BudgieMenuWindow(settings, widget);
        popover.bind_property("visible", widget, "active");

        widget.button_press_event.connect((e)=> {
            if (e.button != 1) {
                return Gdk.EVENT_PROPAGATE;
            }
            if (popover.get_visible()) {
                popover.hide();
            } else {
                popover.get_child().show_all();
                this.manager.show_popover(widget);
            }
            return Gdk.EVENT_STOP;
        });

        popover.get_child().show_all();

        supported_actions = Budgie.PanelAction.MENU;

        add(widget);
        show_all();
        layout.valign = Gtk.Align.CENTER;
        valign = Gtk.Align.FILL;
        halign = Gtk.Align.FILL;
        on_settings_changed("enable-menu-label");
        on_settings_changed("menu-icon");
        on_settings_changed("menu-label");

        /* Potentially reload icon on pixel size jumps */
        panel_size_changed.connect((p,i,s)=> {
            if (this.pixel_size != i) {
                this.pixel_size = (int)i;
                this.on_settings_changed("menu-icon");
            }
        });

        popover.key_release_event.connect((e)=> {
            if (e.keyval == Gdk.Key.Escape) {
                popover.hide();
            }
            return Gdk.EVENT_PROPAGATE;
        });
    }

    public override void panel_position_changed(Budgie.PanelPosition position)
    {
        this.panel_position = position;
        bool vertical = (position == Budgie.PanelPosition.LEFT || position == Budgie.PanelPosition.RIGHT);
        int margin = vertical ? 0 : 3;
        img.set_margin_end(margin);
        on_settings_changed("enable-menu-label");
    }

    public override void invoke_action(Budgie.PanelAction action)
    {
        if ((action & Budgie.PanelAction.MENU) != 0) {
            if (popover.get_visible()) {
                popover.hide();
            } else {
                popover.get_child().show_all();
                this.manager.show_popover(widget);
            }
        }
    }

    protected void on_settings_changed(string key)
    {
        bool should_show = true;

        switch (key)
        {
            case "menu-icon":
                string? icon = settings.get_string(key);
                if ("/" in icon) {
                    try {
                        Gdk.Pixbuf pixbuf = new Gdk.Pixbuf.from_file(icon);
                        img.set_from_pixbuf(pixbuf.scale_simple(this.pixel_size, this.pixel_size, Gdk.InterpType.BILINEAR));
                    } catch (Error e) {
                        warning("Failed to update Budgie Menu applet icon: %s", e.message);
                        img.set_from_icon_name("view-grid-symbolic", Gtk.IconSize.INVALID); // Revert to view-grid-symbolic
                    }
                } else if (icon == "") {
                    should_show = false;
                } else {
                    img.set_from_icon_name(icon, Gtk.IconSize.INVALID);
                }
                img.set_pixel_size(this.pixel_size);
                img.set_visible(should_show);
                break;
            case "menu-label":
                label.set_label(settings.get_string(key));
                break;
            case "enable-menu-label":
                bool visible = (panel_position == Budgie.PanelPosition.TOP ||
                                panel_position == Budgie.PanelPosition.BOTTOM) &&
                                settings.get_boolean(key);
                label.set_visible(visible);
                break;
            default:
                break;
        }
    }

    public override void update_popovers(Budgie.PopoverManager? manager)
    {
        this.manager = manager;
        manager.register_popover(widget, popover);
    }

} // End class

[ModuleInit]
public void peas_register_types(TypeModule module)
{
    // boilerplate - all modules need this
    var objmodule = module as Peas.ObjectModule;
    objmodule.register_extension_type(typeof(Budgie.Plugin), typeof(BudgieMenu));
}

/*
 * Editor modelines  -  https://www.wireshark.org/tools/modelines.html
 *
 * Local variables:
 * c-basic-offset: 4
 * tab-width: 4
 * indent-tabs-mode: nil
 * End:
 *
 * vi: set shiftwidth=4 tabstop=4 expandtab:
 * :indentSize=4:tabSize=4:noTabs=true:
 */

 /*
 * This file is part of budgie-desktop
 *
 * Copyright © 2015-2019 Budgie Desktop Developers
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 */

public class PlacesIndicator : Budgie.Plugin, Peas.ExtensionBase
{
    public Budgie.Applet get_panel_widget(string uuid) {
        return new PlacesIndicatorApplet(uuid);
    }
}

[GtkTemplate (ui = "/com/solus-project/places-indicator/settings.ui")]
public class PlacesIndicatorSettings : Gtk.Grid
{
    [GtkChild]
    private Gtk.Switch? switch_label;

    [GtkChild]
    private Gtk.Switch? switch_places;

    [GtkChild]
    private Gtk.Switch? switch_expand_places;

    [GtkChild]
    private Gtk.Switch? switch_drives;

    [GtkChild]
    private Gtk.Switch? switch_networks;

    private GLib.Settings? settings;

    public PlacesIndicatorSettings(GLib.Settings? settings)
    {
        this.settings = settings;
        settings.bind("show-label", switch_label, "active", GLib.SettingsBindFlags.DEFAULT);
        settings.bind("expand-places", switch_expand_places, "active", GLib.SettingsBindFlags.DEFAULT);
        settings.bind("show-places", switch_places, "active", GLib.SettingsBindFlags.DEFAULT);
        settings.bind("show-drives", switch_drives, "active", GLib.SettingsBindFlags.DEFAULT);
        settings.bind("show-networks", switch_networks, "active", GLib.SettingsBindFlags.DEFAULT);
    }
}

public class PlacesIndicatorApplet : Budgie.Applet
{
    private Gtk.EventBox? ebox;
    private PlacesIndicatorWindow? popover;
    private Gtk.Label label;
    private Gtk.Image image;
    private Budgie.PanelPosition panel_position = Budgie.PanelPosition.BOTTOM;

    private unowned Budgie.PopoverManager? manager = null;
    private GLib.Settings settings;
    public string uuid { public set ; public get; }

    public override Gtk.Widget? get_settings_ui() {
        return new PlacesIndicatorSettings(get_applet_settings(uuid));
    }

    public override bool supports_settings() {
        return true;
    }

    public PlacesIndicatorApplet(string uuid)
    {
        Object(uuid: uuid);

        settings_schema = "com.solus-project.places-indicator";
        settings_prefix = "/com/solus-project/budgie-panel/instance/places-indicator";

        settings = get_applet_settings(uuid);
        settings.changed.connect(on_settings_changed);

        ebox = new Gtk.EventBox();
        Gtk.Box layout = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 0);
        ebox.add(layout);
        image = new Gtk.Image.from_icon_name("folder-symbolic", Gtk.IconSize.MENU);
        layout.pack_start(image, true, true, 3);
        label = new Gtk.Label(_("Places"));
        label.halign = Gtk.Align.START;
        layout.pack_start(label, true, true, 3);

        popover = new PlacesIndicatorWindow(image);

        ebox.button_press_event.connect((e)=> {
            if (e.button != 1) {
                return Gdk.EVENT_PROPAGATE;
            }
            toggle_popover();
            return Gdk.EVENT_STOP;
        });

        popover.get_child().show_all();

        add(ebox);
        show_all();

        on_settings_changed("show-label");
        on_settings_changed("expand-places");
        on_settings_changed("show-places");
        on_settings_changed("show-drives");
        on_settings_changed("show-networks");
    }

    public override void panel_position_changed(Budgie.PanelPosition position)
    {
        this.panel_position = position;
        on_settings_changed("show-label");
    }

    public void toggle_popover()
    {
        if (popover.get_visible()) {
            popover.hide();
        } else {
            popover.get_child().show_all();
            this.manager.show_popover(ebox);
        }
    }

    public override void invoke_action(Budgie.PanelAction action) {
        toggle_popover();
    }

    public override void update_popovers(Budgie.PopoverManager? manager)
    {
        this.manager = manager;
        manager.register_popover(ebox, popover);
    }

    protected void on_settings_changed(string key)
    {
        switch (key)
        {
            case "show-label":
                bool visible = (panel_position == Budgie.PanelPosition.TOP ||
                                panel_position == Budgie.PanelPosition.BOTTOM) &&
                                settings.get_boolean(key);
                label.set_visible(visible);
                break;
            case "expand-places":
                popover.expand_places = settings.get_boolean(key);
                break;
            case "show-places":
                popover.show_places = settings.get_boolean(key);
                break;
            case "show-drives":
                popover.show_drives = settings.get_boolean(key);
                break;
            case "show-networks":
                popover.show_networks = settings.get_boolean(key);
                break;
            default:
                break;
        }
    }
}

[ModuleInit]
public void peas_register_types(TypeModule module)
{
    // boilerplate - all modules need this
    var objmodule = module as Peas.ObjectModule;
    objmodule.register_extension_type(typeof(Budgie.Plugin), typeof(PlacesIndicator));
}

Doesn’t show up in Budgie Desktop Settings. budgie-panel --replace throws:

** (budgie-panel:3392): WARNING **: 13:31:15.501: BudgieMenuWindow.vala:190: Software has no parent directory, not adding to menu

I have it in the /usr/lib… dir (along with all other necessary files, ofc). What’s wrong with this @fossfreedom ?
Line 190: } (with a ton of leading spaces that markdown doesn’t wanna show)