window.py 9.41 KB
# window.py
#
# Copyright 2024 Etersoft
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <https://www.gnu.org/licenses/>.
#
# SPDX-License-Identifier: AGPL-3.0-or-later
from gi.repository import Gtk, Adw, GObject

import gettext

from .widgets.applicationrow import ApplicationRow

from .widgets.logdialog import LogDialog
from .widgets.errordialog import ErrorDialog
from .widgets.flagsdialog import FlagsDialog

gettext.textdomain('eepm-play-gui')
_ = gettext.gettext

from .tools.appsmanager import ApplicationManager

@Gtk.Template(resource_path='/ru/eepm/PlayGUI/window.ui')
class EepmPlayGuiWindow(Adw.ApplicationWindow):

    __gtype_name__ = 'EepmPlayGuiWindow'

    # noinspection PyArgumentList
    is_loading = GObject.Property(type=bool, default=True)

    search_entry = Gtk.Template.Child()
    search_bar = Gtk.Template.Child()
    search_toggle_button = Gtk.Template.Child()
    main_stack = Gtk.Template.Child()
    search_dropdown = Gtk.Template.Child()
    choice_listbox = Gtk.Template.Child()

    reset_button = Gtk.Template.Child()
    apply_button = Gtk.Template.Child()
    flags_button = Gtk.Template.Child()

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.set_title("EPM PLAY")
        self.rows = None

        self.reset_button.connect("clicked", lambda _: self.update_ui())

        self.apply_button.connect("activated", self.on_apply_clicked)

        self.errordialog = ErrorDialog()

        self.flagsdialog = FlagsDialog()
        self.flags_button.connect("clicked", lambda _: self.flagsdialog.present(self))

        self.flagsdialog.add_flags(
            [
                {
                    "flag": "--force",
                    "name": "Force",
                    "description": _("Sometimes it helps to get the latest version of the program. (Not recommended)"),
                },
                {
                    "flag": "--ipfs",
                    "name": "IPFS",
                    "description": _("It helps to get resources that are unavailable from your network"),
                },
                {
                    "flag": "--auto",
                    "name": "Auto",
                    "description": _("The user will not be asked any questions"),
                },
            ]
        )

        self.connect("notify::is-loading", self.on_is_loading_changed)
        self.search_bar.connect_entry(self.search_entry)

        self.search_entry.connect("search-changed", self.on_search_changed)
        self.search_dropdown.connect("notify::selected", self.on_filter_changed)

        self.choice_listbox.set_filter_func(self.listbox_filter_func)

        self.logdialog = LogDialog(win=self)


        self.update_ui()
        
    def on_is_loading_changed (self, obj, _pspec):
        self.main_stack.props.visible_child_name = "loading" if self.props.is_loading else "main"

    def on_applications_loaded(self, applications, error=None):
        if error:
            print(f"Error: {error}")
        else:
            self.applications = applications

    def update_ui(self):
        self.installed_apps = None
        self.applications = None

        self.props.is_loading = True

        self.update_button_status()

        ApplicationManager.get_available_applications(self.on_applications_loaded)
        ApplicationManager.get_installed_applications(self.on_installed_apps_loaded)

    def on_installed_apps_loaded(self, installed_apps, error=None):
        if error:
            print(f"Error: {error}")
        else:
            self.installed_apps = installed_apps
            self.clear_choice_listbox()

            self.rows = {}
            for app in self.applications:
                self.add_application_row(app)

            self.choice_listbox.invalidate_filter()
            self.props.is_loading = False

    def clear_choice_listbox(self):
        self.choice_listbox.remove_all()

    def add_application_row(self, app):
        """Adds an application row to the listbox."""
        row = ApplicationRow(
            app=app,
            is_installed=app['name'] in self.installed_apps,
            on_toggle=self.on_checkbox_toggled
        )
        self.choice_listbox.append(row)
        self.rows[app['name']] = row

    def on_checkbox_toggled(self, app_name, active):
        print(f"{app_name} {'установлен' if active else 'снят'}")
        self.update_button_status()

    def update_button_status(self):
        """Update the button status based on the current selection."""
        to_install, to_remove = self.get_install_remove_lists()

        button_states = {
            (True, True): (_("Remove and install applications"), "suggested-action"),
            (True, False): (_("Install applications"), "suggested-action"),
            (False, True): (_("Remove applications"), "destructive-action"),
            (False, False): (_("Update applications"), "suggested-action"),
        }

        title, css_class = button_states[(bool(to_install), bool(to_remove))]
        self.apply_button.set_title(title)

        # Remove all previous CSS classes in a loop
        for css in ["suggested-action", "destructive-action"]:
            self.apply_button.remove_css_class(css)

        # Add the new CSS class
        self.apply_button.add_css_class(css_class)

    def on_search_changed(self, search_entry):
        self.choice_listbox.invalidate_filter()  # Обновление фильтра при изменении поиска

    def on_filter_changed(self, dropdown, _pspec):
        self.choice_listbox.invalidate_filter()  # Обновление фильтра при изменении фильтра

    def listbox_filter_func(self, row):
        """Функция фильтрации для GtkListBox, которая проверяет текст и состояние фильтра."""
        search_text = self.search_entry.get_text().lower()
        filter_option = self.search_dropdown.get_selected()

        # Получение заголовка и подзаголовка строки
        title = row.get_title().lower() if row.get_title() else ''
        subtitle = row.get_subtitle().lower() if row.get_subtitle() else ''

        # Проверка текста поиска (по имени или описанию)
        matches_search = search_text in title or search_text in subtitle

        # Проверка по фильтру: установлено/не установлено в системе
        app_name = row.get_title() if row.get_title() else ''
        is_installed = app_name in self.installed_apps
        is_changed = row.is_changed()

        if filter_option == 1:  # Installed
            return matches_search and is_installed
        elif filter_option == 2:  # Uninstalled
            return matches_search and not is_installed
        elif filter_option == 3:  # Changed
            return matches_search and is_changed
        else:
            return matches_search  # All

    def on_logdialog_closed(self, error=None):
        if error:
            self.errordialog.present_error(self, error)
        self.update_ui()

    def on_apply_clicked(self, button):
        self.props.is_loading = True  # Show loading message before command execution

        to_install, to_remove = self.get_install_remove_lists()
        full_command = self.build_commands(to_install, to_remove)

        if full_command:
            pkexec_command = f'pkexec sh -c "{full_command}"'
            self.logdialog.run(pkexec_command, on_done=self.on_logdialog_closed)
        else:
            self.logdialog.run(self.flagsdialog.apply_flags(
                "epm play",
                "pkexec epm play --update all",
                ignored_flags=[
                    "--force",
                ]
            ),
                on_done=self.on_logdialog_closed
            )

    def get_install_remove_lists(self):
        if not (self.installed_apps and self.rows):
            return [], []

        to_install = [
            app_name for app_name, row in self.rows.items()
            if row.checkbox.get_active() and app_name not in self.installed_apps
        ]

        to_remove = [
            app_name for app_name, row in self.rows.items()
            if not row.checkbox.get_active() and app_name in self.installed_apps
        ]

        return to_install, to_remove

    def build_commands(self, to_install, to_remove):
        commands = []
        if to_install:
            commands.append(self.flagsdialog.apply_flags("epm play", f"epm play {' '.join(to_install)}"))
        if to_remove:
            commands.append(
                self.flagsdialog.apply_flags(
                    "epm play",
                    f"epm play --remove {' '.join(to_remove)}",

                    ignored_flags=[
                    "--force", "--ipfs"
                    ]
                )
            )

        if not commands:
            return None

        full_command = " && ".join(commands)
        return full_command