/* applications.js
 *
 * 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.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 */

/* exported ApplicationsMenuButton */

const Gettext = imports.gettext;

const { Clutter, Gio, GMenu, GObject, Shell, St } = imports.gi;

const ExtensionUtils = imports.misc.extensionUtils;

const Main = imports.ui.main;
const PanelMenu = imports.ui.panelMenu;
const PopupMenu = imports.ui.popupMenu;

const Me = ExtensionUtils.getCurrentExtension();

const _ = Gettext.domain(Me.metadata['gettext-domain']).gettext;

const APPLICATIONS_ICON = 1;
const APPLICATIONS_TEXT = 2;

const KEY_SHOW_APPLICATIONS = 'show-applications';

const PopupApplicationMenuItem = GObject.registerClass(
class PopupApplicationMenuItem extends PopupMenu.PopupBaseMenuItem {
    _init(app) {
        super._init();
        this._icon = new St.Icon({
            style_class: 'popup-menu-icon',
            x_align: Clutter.ActorAlign.END,
        });
        this.add_child(this._icon);
        this.label = new St.Label({
            text: app.get_name(),
            y_expand: true,
            y_align: Clutter.ActorAlign.CENTER,
        });
        this.add_child(this.label);
        this.label_actor = this.label;
        this._setIcon(app.get_app_info().get_icon());
        this._app = app;
    }

    _setIcon(icon) {
        if (icon instanceof GObject.Object && GObject.type_is_a(icon, Gio.Icon)) {
            this._icon.gicon = icon;
        } else {
            this._icon.icon_name = icon;
        }
    }

    activate(event) {
        let modifiers = event ? event.get_state() : 0;
        let isCtrlPressed = (modifiers & Clutter.ModifierType.CONTROL_MASK) !== 0;
        let openNewWindow = this._app.can_open_new_window() && this._app.state === Shell.AppState.RUNNING && (isCtrlPressed);
        if (openNewWindow) {
            this._app.open_new_window(-1);
        } else {
            this._app.activate();
        }
        Main.overview.hide();
        super.activate(event);
    }
});

var ApplicationsMenuButton = GObject.registerClass(
class ApplicationsMenuButton extends PanelMenu.Button {
    _init(settings) {
        super._init(0.5, _('Applications'), false);
        this.menu.actor.add_style_class_name('panelmanager-menu');
        this._hbox = new St.BoxLayout({ style_class: 'panel-status-indicators-box' });
        this._icon = new St.Icon({
            style_class: 'system-status-icon',
            icon_name: 'view-grid-symbolic',
        });
        this._hbox.add_child(this._icon);
        this._label = new St.Label({
            text: _('Applications'),
            y_expand: true,
            y_align: Clutter.ActorAlign.CENTER,
        });
        this._hbox.add_child(this._label);
        this._hbox.add_child(PopupMenu.arrowIcon(St.Side.BOTTOM));
        this.add_child(this._hbox);
        this._settings = settings;
        this._settingsChangedId = this._settings.connect('changed::%s'.format(KEY_SHOW_APPLICATIONS), this._onChangedShowApplications.bind(this));
        this._onChangedShowApplications();
        this._appSystem = Shell.AppSystem.get_default();
        this._appSystemChangedId = [
            this._appSystem.connect('app-state-changed', this._reloadApplications.bind(this)),
            this._appSystem.connect('installed-changed', this._reloadApplications.bind(this)),
        ];
        this._tree = new GMenu.Tree({ menu_basename: 'applications.menu' });
        this._treeChangedId = this._tree.connect('changed', this._reloadApplications.bind(this));
        this._reloadApplications();
    }

    _getApp(entry) {
        let app = this._appSystem.lookup_app(entry.get_desktop_file_id());
        if (!app) app = new Shell.App({ app_info: entry.get_app_info() });
        return app.get_app_info().should_show() ? app : null;
    }

    _getCategory(appList, appDir) {
        let iter = appDir.iter();
        let nextType;
        while ((nextType = iter.next()) !== GMenu.TreeItemType.INVALID) {
            if (nextType === GMenu.TreeItemType.ENTRY) {
                let app = this._getApp(iter.get_entry());
                if (app) appList.push(app);
            } else if (nextType === GMenu.TreeItemType.DIRECTORY) {
                if (!appDir.get_is_nodisplay()) this._getCategory(appList, iter.get_directory());
            }
        }
    }

    _onChangedShowApplications() {
        let showApplications = this._settings.get_enum(KEY_SHOW_APPLICATIONS);
        this._icon.visible = showApplications !== APPLICATIONS_TEXT;
        this._label.visible = showApplications !== APPLICATIONS_ICON;
    }

    _onDestroy() {
        if (this._treeChangedId) this._tree.disconnect(this._treeChangedId);
        this._appSystemChangedId.forEach(id => {
            this._appSystem.disconnect(id);
        });
        this._settings.disconnect(this._settingsChangedId);
        super._onDestroy();
    }

    _reloadApplications() {
        this.menu.removeAll();
        this._tree.load_sync();
        let root = this._tree.get_root_directory();
        let iter = root.iter();
        let nextType;
        while ((nextType = iter.next()) !== GMenu.TreeItemType.INVALID) {
            switch (nextType) {
            case GMenu.TreeItemType.SEPARATOR:
                this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
                break;
            case GMenu.TreeItemType.ENTRY:
                let app = this._getApp(iter.get_entry());
                if (app) this.menu.addMenuItem(new PopupApplicationMenuItem(app));
                break;
            case GMenu.TreeItemType.DIRECTORY:
                let appDir = iter.get_directory();
                if (appDir.get_is_nodisplay()) continue;
                let item = new PopupMenu.PopupSubMenuMenuItem(appDir.get_name(), true);
                item.icon.gicon = appDir.get_icon();
                let appList = [];
                this._getCategory(appList, appDir);
                appList.sort((a, b) => a.compare_by_name(b));
                appList.forEach(app => {
                    item.menu.addMenuItem(new PopupApplicationMenuItem(app));
                });
                if (item.menu.length > 0) this.menu.addMenuItem(item);
                break;
            default: // Nothing to do
            }
        }
        this.visible = this.menu.numMenuItems > 0;
    }
});
