# -*- coding: utf-8 -*- import logging import gi gi.require_version("Gtk", "3.0") from gi.repository import GLib, Gtk, Pango from jinja2 import Environment from datetime import datetime from math import log from ks_includes.screen_panel import ScreenPanel class BasePanel(ScreenPanel): def __init__(self, screen, title): super().__init__(screen, title) self.current_panel = None self.time_min = -1 self.time_format = self._config.get_main_config().getboolean("24htime", True) self.time_update = None self.titlebar_items = [] self.titlebar_name_type = None self.current_extruder = None self.last_usage_report = datetime.now() self.usage_report = 0 # Action bar buttons abscale = self.bts * 1.1 self.control['back'] = self._gtk.Button('back', scale=abscale) self.control['back'].connect("clicked", self.back) self.control['home'] = self._gtk.Button('main', scale=abscale) self.control['home'].connect("clicked", self._screen._menu_go_back, True) for control in self.control: self.set_control_sensitive(False, control) self.control['estop'] = self._gtk.Button('emergency', scale=abscale) self.control['estop'].connect("clicked", self.emergency_stop) self.control['estop'].set_no_show_all(True) self.shutdown = { "name": None, "panel": "shutdown", "icon": "shutdown", } self.control['shutdown'] = self._gtk.Button('shutdown', scale=abscale) self.control['shutdown'].connect("clicked", self.menu_item_clicked, self.shutdown) self.control['shutdown'].set_no_show_all(True) self.control['printer_select'] = self._gtk.Button('shuffle', scale=abscale) self.control['printer_select'].connect("clicked", self._screen.show_printer_select) self.control['printer_select'].set_no_show_all(True) self.shorcut = { "name": "Macros", "panel": "gcode_macros", "icon": "custom-script", } self.control['shortcut'] = self._gtk.Button(self.shorcut['icon'], scale=abscale) self.control['shortcut'].connect("clicked", self.menu_item_clicked, self.shorcut) self.control['shortcut'].set_no_show_all(True) # Any action bar button should close the keyboard for item in self.control: self.control[item].connect("clicked", self._screen.remove_keyboard) # Action bar self.action_bar = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=5) if self._screen.vertical_mode: self.action_bar.set_hexpand(True) self.action_bar.set_vexpand(False) else: self.action_bar.set_hexpand(False) self.action_bar.set_vexpand(True) self.action_bar.get_style_context().add_class('action_bar') self.action_bar.set_size_request(self._gtk.action_bar_width, self._gtk.action_bar_height) self.action_bar.add(self.control['back']) self.action_bar.add(self.control['home']) self.action_bar.add(self.control['printer_select']) self.action_bar.add(self.control['shortcut']) self.action_bar.add(self.control['estop']) self.action_bar.add(self.control['shutdown']) self.show_printer_select(len(self._config.get_printers()) > 1) # Titlebar # This box will be populated by show_heaters self.control['temp_box'] = Gtk.Box(spacing=10) self.titlelbl = Gtk.Label(hexpand=True, halign=Gtk.Align.CENTER, ellipsize=Pango.EllipsizeMode.END) self.control['time'] = Gtk.Label(label="00:00 AM") self.control['time_box'] = Gtk.Box(halign=Gtk.Align.END) self.control['time_box'].pack_end(self.control['time'], True, True, 10) self.titlebar = Gtk.Box(spacing=5, valign=Gtk.Align.CENTER) self.titlebar.get_style_context().add_class("title_bar") self.titlebar.add(self.control['temp_box']) self.titlebar.add(self.titlelbl) self.titlebar.add(self.control['time_box']) self.set_title(title) # Main layout self.main_grid = Gtk.Grid() if self._screen.vertical_mode: self.main_grid.attach(self.titlebar, 0, 0, 1, 1) self.main_grid.attach(self.content, 0, 1, 1, 1) self.main_grid.attach(self.action_bar, 0, 2, 1, 1) self.action_bar.set_orientation(orientation=Gtk.Orientation.HORIZONTAL) else: self.main_grid.attach(self.action_bar, 0, 0, 1, 2) self.action_bar.set_orientation(orientation=Gtk.Orientation.VERTICAL) self.main_grid.attach(self.titlebar, 1, 0, 1, 1) self.main_grid.attach(self.content, 1, 1, 1, 1) self.update_time() def show_heaters(self, show=True): try: for child in self.control['temp_box'].get_children(): self.control['temp_box'].remove(child) devices = self._printer.get_temp_devices() if not show or not devices: return img_size = self._gtk.img_scale * self.bts for device in devices: self.labels[device] = Gtk.Label(ellipsize=Pango.EllipsizeMode.START) self.labels[f'{device}_box'] = Gtk.Box() icon = self.get_icon(device, img_size) if icon is not None: self.labels[f'{device}_box'].pack_start(icon, False, False, 3) self.labels[f'{device}_box'].pack_start(self.labels[device], False, False, 0) # Limit the number of items according to resolution nlimit = int(round(log(self._screen.width, 10) * 5 - 10.5)) n = 0 if len(self._printer.get_tools()) > (nlimit - 1): self.current_extruder = self._printer.get_stat("toolhead", "extruder") if self.current_extruder and f"{self.current_extruder}_box" in self.labels: self.control['temp_box'].add(self.labels[f"{self.current_extruder}_box"]) else: self.current_extruder = False for device in devices: if n >= nlimit: break if device.startswith("extruder") and self.current_extruder is False: self.control['temp_box'].add(self.labels[f"{device}_box"]) n += 1 elif device.startswith("heater"): self.control['temp_box'].add(self.labels[f"{device}_box"]) n += 1 for device in devices: # Users can fill the bar if they want if n >= nlimit + 1: break name = device.split()[1] if len(device.split()) > 1 else device for item in self.titlebar_items: if name == item: self.control['temp_box'].add(self.labels[f"{device}_box"]) n += 1 break self.control['temp_box'].show_all() except Exception as e: logging.debug(f"Couldn't create heaters box: {e}") def get_icon(self, device, img_size): if device.startswith("extruder"): if self._printer.extrudercount > 1: if device == "extruder": device = "extruder0" return self._gtk.Image(f"extruder-{device[8:]}", img_size, img_size) return self._gtk.Image("extruder", img_size, img_size) elif device.startswith("heater_bed"): return self._gtk.Image("bed", img_size, img_size) # Extra items elif self.titlebar_name_type is not None: # The item has a name, do not use an icon return None elif device.startswith("temperature_fan"): return self._gtk.Image("fan", img_size, img_size) elif device.startswith("heater_generic"): return self._gtk.Image("heater", img_size, img_size) else: return self._gtk.Image("heat-up", img_size, img_size) def activate(self): if self.time_update is None: self.time_update = GLib.timeout_add_seconds(1, self.update_time) def add_content(self, panel): printing = self._printer and self._printer.state in {"printing", "paused"} connected = self._printer and self._printer.state not in {'disconnected', 'startup', 'shutdown', 'error'} self.control['estop'].set_visible(printing) self.control['shutdown'].set_visible(not printing) self.show_shortcut(connected) self.show_heaters(connected) for control in ('back', 'home'): self.set_control_sensitive(len(self._screen._cur_panels) > 1, control=control) self.current_panel = panel self.set_title(panel.title) self.content.add(panel.content) def back(self, widget=None): if self.current_panel is None: return self._screen.remove_keyboard() if hasattr(self.current_panel, "back") \ and not self.current_panel.back() \ or not hasattr(self.current_panel, "back"): self._screen._menu_go_back() def process_update(self, action, data): if action == "notify_proc_stat_update": cpu = data["system_cpu_usage"]["cpu"] memory = (data["system_memory"]["used"] / data["system_memory"]["total"]) * 100 error = "message_popup_error" ctx = self.titlebar.get_style_context() msg = f"CPU: {cpu:2.0f}% RAM: {memory:2.0f}%" if cpu > 80 or memory > 85: if self.usage_report < 3: self.usage_report += 1 return self.last_usage_report = datetime.now() if not ctx.has_class(error): ctx.add_class(error) self._screen.log_notification(f"{self._screen.connecting_to_printer}: {msg}", 2) self.titlelbl.set_label(msg) elif ctx.has_class(error): if (datetime.now() - self.last_usage_report).seconds < 5: self.titlelbl.set_label(msg) return self.usage_report = 0 ctx.remove_class(error) self.titlelbl.set_label(f"{self._screen.connecting_to_printer}") return if action == "notify_update_response": if self.update_dialog is None: self.show_update_dialog() if 'message' in data: self.labels['update_progress'].set_text( f"{self.labels['update_progress'].get_text().strip()}\n" f"{data['message']}\n") if 'complete' in data and data['complete']: logging.info("Update complete") if self.update_dialog is not None: try: self.update_dialog.set_response_sensitive(Gtk.ResponseType.OK, True) self.update_dialog.get_widget_for_response(Gtk.ResponseType.OK).show() except AttributeError: logging.error("error trying to show the updater button the dialog might be closed") self._screen.updating = False for dialog in self._screen.dialogs: self._gtk.remove_dialog(dialog) return if action != "notify_status_update" or self._screen.printer is None: return for device in self._printer.get_temp_devices(): temp = self._printer.get_stat(device, "temperature") if temp is not None and device in self.labels: name = "" if not (device.startswith("extruder") or device.startswith("heater_bed")): if self.titlebar_name_type == "full": name = device.split()[1] if len(device.split()) > 1 else device name = f'{self.prettify(name)}: ' elif self.titlebar_name_type == "short": name = device.split()[1] if len(device.split()) > 1 else device name = f"{name[:1].upper()}: " self.labels[device].set_label(f"{name}{temp:.0f}°") if (self.current_extruder and 'toolhead' in data and 'extruder' in data['toolhead'] and data["toolhead"]["extruder"] != self.current_extruder): self.control['temp_box'].remove(self.labels[f"{self.current_extruder}_box"]) self.current_extruder = data["toolhead"]["extruder"] self.control['temp_box'].pack_start(self.labels[f"{self.current_extruder}_box"], True, True, 3) self.control['temp_box'].reorder_child(self.labels[f"{self.current_extruder}_box"], 0) self.control['temp_box'].show_all() return False def remove(self, widget): self.content.remove(widget) def set_control_sensitive(self, value=True, control='shortcut'): self.control[control].set_sensitive(value) def show_shortcut(self, show=True): show = ( show and self._config.get_main_config().getboolean('side_macro_shortcut', True) and self._printer.get_printer_status_data()["printer"]["gcode_macros"]["count"] > 0 ) self.control['shortcut'].set_visible(show) self.set_control_sensitive(self._screen._cur_panels[-1] != self.shorcut['panel']) self.set_control_sensitive(self._screen._cur_panels[-1] != self.shutdown['panel'], control='shutdown') def show_printer_select(self, show=True): self.control['printer_select'].set_visible(show) def set_title(self, title): self.titlebar.get_style_context().remove_class("message_popup_error") if not title: self.titlelbl.set_label(f"{self._screen.connecting_to_printer}") return try: env = Environment(extensions=["jinja2.ext.i18n"], autoescape=True) env.install_gettext_translations(self._config.get_lang()) j2_temp = env.from_string(title) title = j2_temp.render() except Exception as e: logging.debug(f"Error parsing jinja for title: {title}\n{e}") self.titlelbl.set_label(f"{self._screen.connecting_to_printer} | {title}") def update_time(self): now = datetime.now() confopt = self._config.get_main_config().getboolean("24htime", True) if now.minute != self.time_min or self.time_format != confopt: if confopt: self.control['time'].set_text(f'{now:%H:%M }') else: self.control['time'].set_text(f'{now:%I:%M %p}') self.time_min = now.minute self.time_format = confopt return True def set_ks_printer_cfg(self, printer): ScreenPanel.ks_printer_cfg = self._config.get_printer_config(printer) if self.ks_printer_cfg is not None: self.titlebar_name_type = self.ks_printer_cfg.get("titlebar_name_type", None) titlebar_items = self.ks_printer_cfg.get("titlebar_items", None) if titlebar_items is not None: self.titlebar_items = [str(i.strip()) for i in titlebar_items.split(',')] logging.info(f"Titlebar name type: {self.titlebar_name_type} items: {self.titlebar_items}") else: self.titlebar_items = [] def show_update_dialog(self): if self.update_dialog is not None: return button = [{"name": _("Finish"), "response": Gtk.ResponseType.OK}] self.labels['update_progress'] = Gtk.Label(hexpand=True, vexpand=True, ellipsize=Pango.EllipsizeMode.END) self.labels['update_scroll'] = self._gtk.ScrolledWindow(steppers=False) self.labels['update_scroll'].set_property("overlay-scrolling", True) self.labels['update_scroll'].add(self.labels['update_progress']) self.labels['update_scroll'].connect("size-allocate", self._autoscroll) dialog = self._gtk.Dialog(_("Updating"), button, self.labels['update_scroll'], self.finish_updating) dialog.connect("delete-event", self.close_update_dialog) dialog.set_response_sensitive(Gtk.ResponseType.OK, False) dialog.get_widget_for_response(Gtk.ResponseType.OK).hide() self.update_dialog = dialog self._screen.updating = True def finish_updating(self, dialog, response_id): if response_id != Gtk.ResponseType.OK: return logging.info("Finishing update") self._screen.updating = False self._gtk.remove_dialog(dialog) self._screen._menu_go_back(home=True) def close_update_dialog(self, *args): logging.info("Closing update dialog") if self.update_dialog in self._screen.dialogs: self._screen.dialogs.remove(self.update_dialog) self.update_dialog = None self._screen._menu_go_back(home=True)