diff --git a/panels/base_panel.py b/panels/base_panel.py index 889b7b7d6..f4f99d900 100644 --- a/panels/base_panel.py +++ b/panels/base_panel.py @@ -31,6 +31,8 @@ def __init__(self, screen, title=None): self.titlebar_items = [] self.titlebar_name_type = None self.current_extruder = None + self.title_printer = "" + self.title_panel = "" self.last_usage_report = datetime.now() self.usage_report = 0 # Action bar buttons @@ -90,6 +92,7 @@ def __init__(self, screen, title=None): self.control['temp_box'] = Gtk.Box(spacing=10) self.titlelbl = Gtk.Label(hexpand=True, halign=Gtk.Align.CENTER, ellipsize=Pango.EllipsizeMode.END) + self.titlelbl.connect("size-allocate", self.update_title_visibility) self.control['time'] = Gtk.Label(label="00:00 AM") self.control['time_box'] = Gtk.Box(halign=Gtk.Align.END) @@ -370,14 +373,17 @@ def show_printer_select(self, show=True): def set_title(self, title): self.titlebar.get_style_context().remove_class("message_popup_error") if ( - self._screen.connecting_to_printer != "Printer" + self._screen.connecting_to_printer + and self._screen.connecting_to_printer != "Printer" and 'printer_select' not in self._screen._cur_panels ): printer = self._screen.connecting_to_printer else: printer = "" if not title: - self.titlelbl.set_label(f"{printer}") + self.title_printer = printer + self.title_panel = "" + self.update_title_visibility() return try: env = Environment(extensions=["jinja2.ext.i18n"], autoescape=True) @@ -387,7 +393,29 @@ def set_title(self, title): except Exception as e: logging.debug(f"Error parsing jinja for title: {title}\n{e}") - self.titlelbl.set_label(f"{printer} {title}") + self.title_printer = printer + self.title_panel = title + self.update_title_visibility() + + def update_title_visibility(self, *args): + if self.titlebar.get_style_context().has_class("message_popup_error"): + return False + if not self.title_panel: + label = self.title_printer or "" + else: + label = f"{self.title_printer} {self.title_panel}".strip() + if self.title_printer and self._screen.vertical_mode: + allocation = self.titlelbl.get_allocation() + if allocation.width and not self.title_fits(label, allocation.width): + label = self.title_panel + if self.titlelbl.get_label() != label: + self.titlelbl.set_label(label) + return False + + def title_fits(self, text, width): + layout = self.titlelbl.create_pango_layout(text) + text_width, _ = layout.get_pixel_size() + return text_width <= width def update_time(self): now = datetime.now() diff --git a/panels/gcodes.py b/panels/gcodes.py index dc02ea849..eab05dd4e 100644 --- a/panels/gcodes.py +++ b/panels/gcodes.py @@ -221,12 +221,13 @@ def create_item(self, item): # Определяем позиции кнопок в зависимости от типа элемента button_col = 2 if 'filename' in item: + file_on_usb = is_file_on_usb(f"gcodes/{path}") icon.connect("clicked", self.confirm_print, path) image_args = (path, icon, self.thumbsize / 2, True, "file") delete.connect("clicked", self.confirm_delete_file, f"gcodes/{path}") rename.connect("clicked", self.show_rename, f"gcodes/{path}") # Показываем кнопку переноса только если файл находится на USB - if is_file_on_usb(f"gcodes/{path}"): + if file_on_usb: move = Gtk.Button(hexpand=False, vexpand=False, can_focus=False, always_show_image=True) move.get_style_context().add_class("color3") move.set_image(self._gtk.Image("sd", self.list_button_size, self.list_button_size)) @@ -244,8 +245,9 @@ def create_item(self, item): action.set_vexpand(False) action.set_halign(Gtk.Align.END) if self._screen.width >= 400: - row.attach(action, button_col, 0, 1, 2) - else: + if not file_on_usb: + row.attach(action, button_col, 0, 1, 2) + elif not file_on_usb: icon.get_style_context().add_class("color3") row.attach(icon, button_col, 0, 1, 2) elif 'dirname' in item: @@ -489,16 +491,15 @@ def confirm_print(self, widget, filename): buttons = [ {"name": _("Delete"), "response": Gtk.ResponseType.REJECT, "style": 'dialog-error'}, {"name": action, "response": Gtk.ResponseType.OK, "style": 'dialog-primary'}, - *([{"name": _("Settings"), "response": Gtk.ResponseType.HELP, "style": 'dialog-secondary'}] + *([{"name": _("Settings"), "response": Gtk.ResponseType.HELP, "style": 'dialog-info'}] if show_start_settings else []), {"name": _("Cancel"), "response": Gtk.ResponseType.CANCEL, "style": 'dialog-secondary'} ] buttons_usb = [ {"name": _("Delete"), "response": Gtk.ResponseType.REJECT, "style": 'dialog-error'}, - {"name": _("Resave"), "response": Gtk.ResponseType.APPLY, "style": 'dialog-secondary'}, - {"name": action, "response": Gtk.ResponseType.OK, "style": 'dialog-primary'}, - *([{"name": _("Settings"), "response": Gtk.ResponseType.HELP, "style": 'dialog-secondary'}] + {"name": _("Resave"), "response": Gtk.ResponseType.APPLY, "style": 'dialog-primary'}, + *([{"name": _("Settings"), "response": Gtk.ResponseType.HELP, "style": 'dialog-info'}] if show_start_settings else []), {"name": _("Cancel"), "response": Gtk.ResponseType.CANCEL, "style": 'dialog-secondary'} ] @@ -553,6 +554,10 @@ def confirm_print_response(self, dialog, response_id, filename): self._screen.show_panel("print_start_settings") return elif response_id == Gtk.ResponseType.OK: + if is_file_on_usb(f"gcodes/{filename}"): + logging.warning(f"Refusing to print from removable storage: {filename}") + self._screen.show_popup_message(_("Move the file to internal storage before printing"), level=2) + return logging.info(f"Starting print: {filename}") self._screen._ws.klippy.print_start(filename) elif response_id == Gtk.ResponseType.APPLY: diff --git a/panels/network.py b/panels/network.py index 937834af6..eae86a0bb 100644 --- a/panels/network.py +++ b/panels/network.py @@ -64,11 +64,27 @@ def __init__(self, screen, title): self.interface = self.sdbus_nm.get_primary_interface() logging.info(f"Primary interface: {self.interface}") + self.is_ap_mode = False - self.labels['interface'] = Gtk.Label(hexpand=True) - self.labels['ip'] = Gtk.Label(hexpand=True) + self.labels['interface'] = Gtk.Label( + hexpand=True, + halign=Gtk.Align.START, + wrap=True, + wrap_mode=Pango.WrapMode.WORD_CHAR, + ) + self.labels['interface'].set_xalign(0) + self.labels['interface'].set_max_width_chars(30 if self._screen.vertical_mode else 60) + self.labels['interface'].set_no_show_all(True) + self.labels['ip'] = Gtk.Label( + hexpand=True, + halign=Gtk.Align.START, + wrap=True, + wrap_mode=Pango.WrapMode.WORD_CHAR, + ) + self.labels['ip'].set_xalign(0) + self.labels['ip'].set_max_width_chars(30 if self._screen.vertical_mode else 60) if self.interface is not None: - self.labels['interface'].set_text(_("Interface") + f': {self.interface}') + self.update_interface_display() self.labels['ip'].set_text(f"IP: {self.sdbus_nm.get_ip_address()}") self.reload_button = self._gtk.Button("refresh", None, "color1", self.bts) @@ -84,11 +100,17 @@ def __init__(self, screen, title): ) self.wifi_toggle.connect("notify::active", self.toggle_wifi) - sbox = Gtk.Box(hexpand=True, vexpand=False) - sbox.add(self.labels['interface']) - sbox.add(self.labels['ip']) - sbox.add(self.reload_button) - sbox.add(self.wifi_toggle) + info_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, hexpand=True, vexpand=False) + info_box.add(self.labels['interface']) + info_box.add(self.labels['ip']) + + controls_box = Gtk.Box(spacing=5, hexpand=False, vexpand=False) + controls_box.add(self.reload_button) + controls_box.add(self.wifi_toggle) + + sbox = Gtk.Box(spacing=5, hexpand=True, vexpand=False) + sbox.add(info_box) + sbox.pack_end(controls_box, False, False, 0) scroll = self._gtk.ScrolledWindow() self.labels['main_box'] = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, vexpand=True) @@ -99,7 +121,6 @@ def __init__(self, screen, title): # AP-related attributes (used only if show_ap_toggle is True) self.ap_ssid = self._config.get_main_config().get('ap_ssid', 'zboltprinter') self.ap_password = self._config.get_main_config().get('ap_password', 'zboltprinter') - self.is_ap_mode = False if self.sdbus_nm.wifi: self.labels['main_box'].pack_start(sbox, False, False, 5) @@ -120,8 +141,7 @@ def __init__(self, screen, title): ap_container.add(self.ap_label) ap_container.add(self.ap_toggle) - # Insert AP container at the beginning of sbox - sbox.pack_start(ap_container, False, False, 5) + controls_box.pack_start(ap_container, False, False, 5) # Check initial AP mode state and restore if needed if self.sdbus_nm.is_access_point_mode(): @@ -183,6 +203,8 @@ def add_network(self, bssid): buttons = Gtk.Box(spacing=5) name = Gtk.Label(hexpand=True, halign=Gtk.Align.START, wrap=True, wrap_mode=Pango.WrapMode.WORD_CHAR) + name.set_xalign(0) + name.set_justify(Gtk.Justification.LEFT) if bssid == self.sdbus_nm.get_connected_bssid(): ssid += ' (' + _("Connected") + ')' name.set_markup(f"{ssid}") @@ -194,6 +216,8 @@ def add_network(self, bssid): buttons.add(connect) info = Gtk.Label(halign=Gtk.Align.START) + info.set_xalign(0) + info.set_justify(Gtk.Justification.LEFT) labels = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, vexpand=True, halign=Gtk.Align.START, valign=Gtk.Align.CENTER) labels.add(name) @@ -394,18 +418,20 @@ def show_add_network(self, widget, ssid): def update_all_networks(self): self.interface = self.sdbus_nm.get_primary_interface() - self.labels['interface'].set_text(_("Interface") + f': {self.interface}') - self.update_ip_display() # Check if AP mode changed externally ap_mode = self.sdbus_nm.is_access_point_mode() if ap_mode != self.is_ap_mode: self.is_ap_mode = ap_mode - self.ap_toggle.set_active(ap_mode) + if hasattr(self, "ap_toggle"): + self.ap_toggle.set_active(ap_mode) + self.update_ip_display() if ap_mode: self.update_ap_display() return True + self.update_ip_display() + # If in AP mode, don't update network list if self.is_ap_mode: return True @@ -464,6 +490,14 @@ def update_single_network_info(self): self.labels['networkinfo'].show_all() return True + def update_interface_display(self): + if self.is_ap_mode: + self.labels['interface'].hide() + return + if self.interface is not None: + self.labels['interface'].set_text(_("Interface") + f': {self.interface}') + self.labels['interface'].show() + def reload_networks(self, widget=None): if self.is_ap_mode: return @@ -620,6 +654,7 @@ def update_ap_display(self): def update_ip_display(self): """Update IP address display""" + self.update_interface_display() if self.is_ap_mode: ip_info = self.sdbus_nm.get_ap_mode_ips() wifi_ip = ip_info.get("wifi", "?") diff --git a/panels/system.py b/panels/system.py index 28553b88c..a773d3b99 100644 --- a/panels/system.py +++ b/panels/system.py @@ -2,13 +2,14 @@ import os import subprocess import shutil +import threading import zipfile from datetime import datetime import gi gi.require_version("Gtk", "3.0") -from gi.repository import Gtk +from gi.repository import Gtk, GLib from ks_includes.screen_panel import ScreenPanel @@ -272,7 +273,29 @@ def _first_key(d, *keys, default=None): f.write(f"- Total filament used: {filament_mm_str} ({filament_m_str})\n") def export_logs(self, widget): + self._gtk.Button_busy(widget, True) + threading.Thread(target=self._export_logs_thread, args=(widget,), daemon=True).start() + + def _export_logs_thread(self, widget): + """Экспортирует журналы на съемный носитель""" + messages = [] + try: + messages = self._export_logs() + except Exception as e: + logging.error(f"Error exporting logs: {e}") + messages = [(_("Error exporting logs") + f": {str(e)}", 3)] + finally: + GLib.idle_add(self._finish_export_logs, widget, messages) + + def _finish_export_logs(self, widget, messages): + self._gtk.Button_busy(widget, False) + for message, level in messages: + self._screen.show_popup_message(message, level=level) + return False + + def _export_logs(self): """Экспортирует журналы на съемный носитель""" + messages = [] # Ищем примонтированное устройство mounted_device = self.find_mounted_device() @@ -285,9 +308,8 @@ def export_logs(self, widget): export_dest_dir = mounted_device else: export_dest_dir = logs_dir - self._screen.show_popup_message( - _("No removable device found") + "\n" + _("Saving to logs folder"), - level=2 + messages.append( + (_("No removable device found") + "\n" + _("Saving to logs folder"), 2) ) logging.warning( "No mounted device found in /home/pi/printer_data/gcodes/. Saving to ~/printer_data/logs" @@ -388,9 +410,8 @@ def export_logs(self, widget): shutil.rmtree(temp_dir) # Показываем уведомление об успехе - self._screen.show_popup_message( - _("Logs saved successfully") + f"\n{dest_zip_path}", - level=1 + messages.append( + (_("Logs saved successfully") + f"\n{dest_zip_path}", 1) ) # Отмонтируем устройство @@ -410,17 +431,15 @@ def export_logs(self, widget): logging.error(f"Error unmounting device: {e}") except Exception as e: - logging.error(f"Error exporting logs: {e}") - self._screen.show_popup_message( - _("Error exporting logs") + f": {str(e)}", - level=3 - ) # Удаляем временную директорию в случае ошибки if os.path.exists(temp_dir): try: shutil.rmtree(temp_dir) except Exception: pass + raise + + return messages def process_update(self, action, data): if not self.sysinfo: