Files
CreatBotKlipperScreen/panels/gcode_macros.py
Alfredo Monclus 7b857ff991 fix: autoscroll now works if the entry is at the very bottom of the scroll
the delay is needed because the osk will change the scrolledwindow adjustment, and it's only possible to scroll down more after the keyboard has been shown
2024-09-20 19:16:21 -03:00

188 lines
7.6 KiB
Python

import logging
import re
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk, GLib, Pango
from ks_includes.screen_panel import ScreenPanel
class Panel(ScreenPanel):
def __init__(self, screen, title):
title = title or _("Macros")
super().__init__(screen, title)
self.sort_reverse = False
self.sort_btn = self._gtk.Button("arrow-up", _("Name"), "color1", self.bts, Gtk.PositionType.RIGHT, 1)
self.sort_btn.connect("clicked", self.change_sort)
self.sort_btn.set_hexpand(True)
self.sort_btn.get_style_context().add_class("buttons_slim")
self.options = {}
self.macros = {}
self.menu = ['macros_menu']
adjust = self._gtk.Button("settings", " " + _("Settings"), "color2", self.bts, Gtk.PositionType.LEFT, 1)
adjust.get_style_context().add_class("buttons_slim")
adjust.connect("clicked", self.load_menu, 'options', _("Settings"))
adjust.set_hexpand(False)
sbox = Gtk.Box(vexpand=False)
sbox.pack_start(self.sort_btn, True, True, 5)
sbox.pack_start(adjust, True, True, 5)
self.labels['macros_list'] = self._gtk.ScrolledWindow()
self.labels['macros'] = Gtk.Grid()
self.labels['macros_list'].add(self.labels['macros'])
self.labels['macros_menu'] = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, vexpand=True)
self.labels['macros_menu'].pack_start(sbox, False, False, 0)
self.labels['macros_menu'].pack_start(self.labels['macros_list'], True, True, 0)
self.content.add(self.labels['macros_menu'])
self.labels['options_menu'] = self._gtk.ScrolledWindow()
self.labels['options'] = Gtk.Grid()
self.labels['options_menu'].add(self.labels['options'])
def activate(self):
self.reload_macros()
def add_gcode_macro(self, macro):
section = self._printer.get_macro(macro)
if section:
if "rename_existing" in section:
return
if "gcode" in section:
gcode = section["gcode"].split("\n")
else:
logging.error(f"gcode not found in {macro}\n{section}")
return
else:
logging.debug(f"Couldn't load {macro}\n{section}")
return
name = Gtk.Label(hexpand=True, vexpand=True, halign=Gtk.Align.START, valign=Gtk.Align.CENTER,
wrap=True, wrap_mode=Pango.WrapMode.WORD_CHAR)
name.set_markup(f"<big><b>{macro}</b></big>")
btn = self._gtk.Button("resume", style="color3")
btn.connect("clicked", self.run_gcode_macro, macro)
btn.set_hexpand(False)
btn.set_halign(Gtk.Align.END)
labels = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
labels.add(name)
row = Gtk.Box(spacing=5)
row.get_style_context().add_class("frame-item")
row.add(labels)
row.add(btn)
self.macros[macro] = {
"row": row,
"params": {},
}
pattern = re.compile(r'params\.(?P<param>[a-zA-Z0-9_]+)'
r'(?:\s*\|\s*default\(\s*(?P<default>[^\)]+)\s*\))?'
r'(?:\s*\|\s*(?P<hint>[a-zA-Z]+))?')
for line in gcode:
if line.startswith("{") and "params." in line:
result = re.search(pattern, line)
if result:
result = result.groupdict()
default = result.get("default", "")
hint = result.get("hint", "")
entry = Gtk.Entry(placeholder_text=default)
if hint in ("int", "float"):
entry.set_input_purpose(Gtk.InputPurpose.DIGITS)
entry.get_style_context().add_class("active")
else:
entry.set_input_purpose(Gtk.InputPurpose.ALPHA)
icon = self._gtk.PixbufFromIcon("hashtag")
entry.set_icon_from_pixbuf(Gtk.EntryIconPosition.SECONDARY, icon)
entry.connect("icon-press", self.on_icon_pressed)
self.macros[macro]["params"].update({result["param"]: entry})
for param in self.macros[macro]["params"]:
labels.add(Gtk.Label(param))
self.macros[macro]["params"][param].connect("focus-in-event", self.show_keyboard)
self.macros[macro]["params"][param].connect("focus-out-event", self._screen.remove_keyboard)
labels.add(self.macros[macro]["params"][param])
def show_keyboard(self, entry, event):
self._screen.show_keyboard(entry, event)
GLib.timeout_add(100, self.scroll_to_entry, entry)
def scroll_to_entry(self, entry):
self.labels['macros_list'].get_vadjustment().set_value(
entry.get_allocation().y - 50
)
def on_icon_pressed(self, entry, icon_pos, event):
entry.grab_focus()
self._screen.remove_keyboard()
if entry.get_input_purpose() == Gtk.InputPurpose.ALPHA:
entry.set_input_purpose(Gtk.InputPurpose.DIGITS)
entry.get_style_context().add_class("active")
else:
entry.set_input_purpose(Gtk.InputPurpose.ALPHA)
entry.get_style_context().remove_class("active")
self._screen.show_keyboard(entry)
def run_gcode_macro(self, widget, macro):
params = ""
for param in self.macros[macro]["params"]:
self.macros[macro]["params"][param].set_sensitive(False) # Move focus to prevent
self.macros[macro]["params"][param].set_sensitive(True) # reopening the osk
value = self.macros[macro]["params"][param].get_text()
if value:
if re.findall(r'[G|M]\d{1,3}', macro):
params += f' {param}{value}'
else:
params += f' {param}={value}'
self._screen.show_popup_message(f"{macro} {params}", 1)
self._screen._send_action(widget, "printer.gcode.script", {"script": f"{macro}{params}"})
def change_sort(self, widget):
self.sort_reverse ^= True
if self.sort_reverse:
self.sort_btn.set_image(self._gtk.Image("arrow-down", self._gtk.img_scale * self.bts))
else:
self.sort_btn.set_image(self._gtk.Image("arrow-up", self._gtk.img_scale * self.bts))
self.sort_btn.show()
GLib.idle_add(self.reload_macros)
def reload_macros(self):
self.labels['macros'].remove_column(0)
self.macros = {}
self.options = {}
self.labels['options'].remove_column(0)
self.load_gcode_macros()
return False
def load_gcode_macros(self):
for macro in self._printer.get_gcode_macros():
self.options[macro] = {
"name": macro,
"section": f"displayed_macros {self._screen.connected_printer}",
"type": "binary"
}
show = self._config.get_config().getboolean(self.options[macro]["section"], macro.lower(), fallback=True)
if macro not in self.macros and show:
self.add_gcode_macro(macro)
for macro in list(self.options):
self.add_option('options', self.options, macro, self.options[macro])
macros = sorted(self.macros, reverse=self.sort_reverse, key=str.casefold)
for macro in macros:
pos = macros.index(macro)
self.labels['macros'].insert_row(pos)
self.labels['macros'].attach(self.macros[macro]['row'], 0, pos, 1, 1)
self.labels['macros'].show_all()
def back(self):
if len(self.menu) > 1:
self.unload_menu()
self.reload_macros()
return True
return False