import re import gi gi.require_version("Gtk", "4.0") gi.require_version("Adw", "1") from gi.repository import Adw, GObject, Gtk class UserPage(Adw.Bin): __gsignals__ = { "validity-changed": (GObject.SignalFlags.RUN_FIRST, None, (bool,)), } def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._is_valid = False clamp = Adw.Clamp() clamp.set_maximum_size(500) self.set_child(clamp) box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) box.set_spacing(24) box.set_valign(Gtk.Align.CENTER) box.set_margin_top(24) box.set_margin_bottom(24) clamp.set_child(box) title = Gtk.Label(label="Create User Account") title.add_css_class("title-1") box.append(title) # Form Group group = Adw.PreferencesGroup() box.append(group) self.fullname_row = Adw.EntryRow() self.fullname_row.set_title("Full Name") self.fullname_row.set_text("Administrator") self.fullname_row.connect("notify::text", self.on_fullname_changed) self.fullname_row.connect("notify::text", self.on_input_changed) group.add(self.fullname_row) self.username_row = Adw.EntryRow() self.username_row.set_title("Username") self.username_row.set_text("admin") self.username_handler_id = self.username_row.connect( "notify::text", self.on_username_changed ) self.username_row.connect("notify::text", self.on_input_changed) group.add(self.username_row) self.password_row = Adw.PasswordEntryRow() self.password_row.set_title("Password") self.password_row.connect("notify::text", self.on_input_changed) group.add(self.password_row) self.confirm_row = Adw.PasswordEntryRow() self.confirm_row.set_title("Confirm Password") self.confirm_row.connect("notify::text", self.on_input_changed) group.add(self.confirm_row) # Hostname host_group = Adw.PreferencesGroup() host_group.set_margin_top(12) box.append(host_group) self.hostname_row = Adw.EntryRow() self.hostname_row.set_title("Hostname") self.hostname_row.set_text("iridium") self.hostname_row.connect("notify::text", self.on_input_changed) host_group.add(self.hostname_row) self.username_manually_set = False # Initial validation self.validate() def on_fullname_changed(self, entry, _pspec): if self.username_manually_set: return username = entry.get_text().lower() if username == "administrator": username = "admin" for sign in ( " ", "-", "_", ".", ",", ":", ";", "!", "?", "/", "\\", "|", "@", "#", "$", "%", "^", "&", "*", "(", ")", "[", "]", "{", "}", "'", '"', "`", "~", "<", ">", "=", "+", ): username = username.replace(sign, "_") self.username_row.handler_block(self.username_handler_id) self.username_row.set_text(username) self.username_row.handler_unblock(self.username_handler_id) def on_username_changed(self, entry, _pspec): self.username_manually_set = True def on_input_changed(self, *args): self.validate() def validate(self): valid = True # Username validation username = self.username_row.get_text() if not username: valid = False self.username_row.add_css_class("error") elif not re.match(r"^[a-z_][a-z0-9_-]*$", username): valid = False self.username_row.add_css_class("error") else: self.username_row.remove_css_class("error") # Password validation pw = self.password_row.get_text() confirm = self.confirm_row.get_text() if not pw: valid = False # Don't show error for empty password initially, just invalidate # It looks a lot nicer like that if pw != confirm: valid = False self.confirm_row.add_css_class("error") else: self.confirm_row.remove_css_class("error") # Hostname validation hostname = self.hostname_row.get_text() if not hostname: valid = False self.hostname_row.add_css_class("error") else: self.hostname_row.remove_css_class("error") if self._is_valid != valid: self._is_valid = valid self.emit("validity-changed", valid) def is_valid(self): return self._is_valid def get_user_info(self): return { "fullname": self.fullname_row.get_text(), "username": self.username_row.get_text(), "password": self.password_row.get_text(), "hostname": self.hostname_row.get_text(), }