# 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 import gettext gettext.textdomain('eepm-play-gui') _ = gettext.gettext from .appsmanager import ApplicationManager from .command_runner import CommandRunner class ApplicationRow(Adw.ActionRow): def __init__(self, app, is_installed, on_toggle): super().__init__(title=app['name'], subtitle=app['dscr']) self.app = app self.is_installed = is_installed self.checkbox = Gtk.CheckButton() self.checkbox.add_css_class("selection-mode") self.checkbox.set_active(is_installed) self.checkbox.connect("toggled", self.on_checkbox_toggled) self.add_suffix(self.checkbox) self.on_toggle = on_toggle # Callback for when the checkbox is toggled def on_checkbox_toggled(self, checkbox): self.on_toggle(self.app['name'], checkbox.get_active()) self.update_row_css() # Update the row's style when toggled def is_changed(self): """Determine if the checkbox state has changed compared to the installed state.""" return self.checkbox.get_active() != self.is_installed def update_row_css(self): """Update the CSS classes for the row based on the checkbox state.""" for css in ["marked-for-install", "marked-for-removal", "unchanged"]: self.remove_css_class(css) # Add the appropriate class based on the current checkbox state if self.is_changed(): if self.checkbox.get_active(): self.add_css_class("marked-for-install") print("marked-for-install") else: self.add_css_class("marked-for-removal") print("marked-for-removal") else: # self.add_css_class("unchanged") print("marked-unchanged") @Gtk.Template(resource_path='/ru/eepm/PlayGUI/logdialog.ui') class LogDialog(Adw.Dialog): __gtype_name__ = 'LogDialog' logdialog_textview = Gtk.Template.Child() def __init__(self, win, **kwargs): super().__init__(**kwargs) self.win = win def run(self, command, on_done): self.present(self.win) # Создание и передача функции обратного вызова для обновления UI runner = CommandRunner(on_done=on_done) runner.run_command(command, self) @Gtk.Template(resource_path='/ru/eepm/PlayGUI/window.ui') class EepmPlayGuiWindow(Adw.ApplicationWindow): __gtype_name__ = 'EepmPlayGuiWindow' search_entry = Gtk.Template.Child() search_dropdown = Gtk.Template.Child() loading_spinner = Gtk.Template.Child() choice_listbox = Gtk.Template.Child() apply_button = Gtk.Template.Child() def __init__(self, **kwargs): super().__init__(**kwargs) self.set_title("EPM PLAY") self.checkboxes = None self.apply_button.connect("activated", self.on_apply_clicked) 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.dialog = LogDialog(win=self) self.update_ui() def show_loading_spinner(self): self.loading_spinner.set_visible(True) self.choice_listbox.set_visible(False) # Скрыть группу настроек во время загрузки def hide_loading_spinner(self): self.loading_spinner.set_visible(False) self.choice_listbox.set_visible(True) 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.show_loading_spinner() # Show loading again for installed apps 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.checkboxes = {} for app in self.applications: self.add_application_row(app) self.choice_listbox.invalidate_filter() self.hide_loading_spinner() 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.checkboxes[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_apply_clicked(self, button): commands = None self.show_loading_spinner() # Показать сообщение о загрузке перед выполнением команды to_install, to_remove = self.get_install_remove_lists() commands = self.build_commands(to_install, to_remove) if commands: full_command = " && ".join(commands) pkexec_command = f'pkexec sh -c "{full_command}"' self.dialog.run(pkexec_command, on_done=self.update_ui) else: self.dialog.run("pkexec epm play --update all", on_done=self.update_ui) def get_install_remove_lists(self): if self.installed_apps and self.checkboxes: to_install = [app_name for app_name, row in self.checkboxes.items() if row.checkbox.get_active() and app_name not in self.installed_apps] to_remove = [app_name for app_name, row in self.checkboxes.items() if not row.checkbox.get_active() and app_name in self.installed_apps] return to_install, to_remove else: return False, False def build_commands(self, to_install, to_remove): commands = [] if to_install: commands.append(f"epm play --auto {' '.join(to_install)}") if to_remove: commands.append(f"epm play --auto --remove {' '.join(to_remove)}") return commands