From fae1add591c1abb37a94d27b0a3cc54eef70151d Mon Sep 17 00:00:00 2001 From: Jonatas Esteves Date: Mon, 6 Jan 2025 21:27:13 -0300 Subject: [PATCH] envvars-dialog: Allow to paste var=value and fix validation * Rework the envvars dialog to allow copy/pasting complete variable assignments instead of having to input first the name and later the value. * Fix the input validation which had some inverted logic and make it behave as expected. * Make the panel leaner by removing unnecessary headers and periods at the end of labels. --- bottles/frontend/ui/dialog-env-vars.blp | 10 ++-- bottles/frontend/ui/env-var-entry.blp | 1 - bottles/frontend/utils/gtk.py | 13 ++--- bottles/frontend/utils/meson.build | 1 + bottles/frontend/utils/sh.py | 30 +++++++++++ bottles/frontend/windows/envvars.py | 68 +++++++++++++++---------- 6 files changed, 83 insertions(+), 40 deletions(-) create mode 100644 bottles/frontend/utils/sh.py diff --git a/bottles/frontend/ui/dialog-env-vars.blp b/bottles/frontend/ui/dialog-env-vars.blp index 46d47a97c0b..bace43767ee 100644 --- a/bottles/frontend/ui/dialog-env-vars.blp +++ b/bottles/frontend/ui/dialog-env-vars.blp @@ -24,17 +24,15 @@ template $EnvVarsDialog: Adw.Window { Adw.PreferencesPage { Adw.PreferencesGroup { - description: _("Environment variables are dynamic-named value that can affect the way running processes will behave on your bottle."); + description: _("Environment variables are dynamic-named values that can affect the way running processes will behave in your bottle"); - Adw.EntryRow entry_name { - title: _("Variable Name"); + Adw.EntryRow entry_new_var { + title: _("New Variable"); show-apply-button: true; } } - Adw.PreferencesGroup group_vars { - title: _("Existing Variables"); - } + Adw.PreferencesGroup group_vars {} } } } diff --git a/bottles/frontend/ui/env-var-entry.blp b/bottles/frontend/ui/env-var-entry.blp index 014cd815d6e..8e4afe55d21 100644 --- a/bottles/frontend/ui/env-var-entry.blp +++ b/bottles/frontend/ui/env-var-entry.blp @@ -2,7 +2,6 @@ using Gtk 4.0; using Adw 1; template $EnvVarEntry: Adw.EntryRow { - title: _("Value"); show-apply-button: true; Button btn_remove { diff --git a/bottles/frontend/utils/gtk.py b/bottles/frontend/utils/gtk.py index 3c1d9c61096..9da6a722975 100644 --- a/bottles/frontend/utils/gtk.py +++ b/bottles/frontend/utils/gtk.py @@ -15,28 +15,29 @@ # along with this program. If not, see . # -import re from typing import Optional from functools import wraps from inspect import signature from gi.repository import GLib, Gtk +from bottles.frontend.utils.sh import ShUtils + class GtkUtils: @staticmethod def validate_entry(entry, extend=None) -> bool: - text = entry.get_text() + var_assignment=entry.get_text() + var_name = ShUtils.split_assignment(var_assignment)[0] if ( - re.search("[@!#$%^&*()<>?/|}{~:.;,'\"]", text) - or len(text) == 0 - or text.isspace() + "=" not in var_assignment + or not ShUtils.is_name(var_name) ): entry.add_css_class("error") return False if extend is not None: - if extend(text): + if not extend(var_name): entry.add_css_class("error") return False diff --git a/bottles/frontend/utils/meson.build b/bottles/frontend/utils/meson.build index cdb7389e52c..5b280588e02 100644 --- a/bottles/frontend/utils/meson.build +++ b/bottles/frontend/utils/meson.build @@ -6,6 +6,7 @@ bottles_sources = [ 'gtk.py', 'common.py', 'filters.py', + 'sh.py', ] install_data(bottles_sources, install_dir: utilsdir) diff --git a/bottles/frontend/utils/sh.py b/bottles/frontend/utils/sh.py new file mode 100644 index 00000000000..b2f93992bcb --- /dev/null +++ b/bottles/frontend/utils/sh.py @@ -0,0 +1,30 @@ +# sh.py +# +# Copyright 2025 The Bottles Contributors +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, in version 3 of the License. +# +# 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# + +import re + +_is_name = re.compile(r'''[_a-zA-Z][_a-zA-Z0-9]*''') + +class ShUtils: + @staticmethod + def is_name(text: str) -> bool: + return bool(_is_name.fullmatch(text)) + + @staticmethod + def split_assignment(text: str) -> tuple[str, str]: + name, _, value = text.partition("=") + return (name, value) diff --git a/bottles/frontend/windows/envvars.py b/bottles/frontend/windows/envvars.py index caca77d9907..9f33210baf0 100644 --- a/bottles/frontend/windows/envvars.py +++ b/bottles/frontend/windows/envvars.py @@ -18,6 +18,7 @@ from gi.repository import Gtk, GLib, Adw from bottles.frontend.utils.gtk import GtkUtils +from bottles.frontend.utils.sh import ShUtils @Gtk.Template(resource_path="/com/usebottles/bottles/env-var-entry.ui") @@ -36,11 +37,10 @@ def __init__(self, parent, env, **kwargs): self.manager = parent.window.manager self.config = parent.config self.env = env - - self.set_title(self.env[0]) - self.set_text(self.env[1]) + self.set_text("=".join(self.env)) # connect signals + self.connect("changed", self.__validate) self.connect("apply", self.__save) self.btn_remove.connect("clicked", self.__remove) @@ -49,18 +49,33 @@ def __save(self, *_args): Change the env var value according to the user input and update the bottle configuration """ + if not self.__valid_name: + return + + new_name, new_value = ShUtils.split_assignment(self.get_text()) self.manager.update_config( config=self.config, - key=self.env[0], - value=self.get_text(), + key=new_name, + value=new_value, scope="Environment_Variables", ) + if new_name != self.env[0]: + self.__remove_config() + + self.env = (new_name, new_value) def __remove(self, *_args): """ Remove the env var from the bottle configuration and destroy the widget """ + self.__remove_config() + self.parent.group_vars.remove(self) + + def __remove_config(self, *_args): + """ + Remove the env var from the bottle configuration + """ self.manager.update_config( config=self.config, key=self.env[0], @@ -68,7 +83,11 @@ def __remove(self, *_args): remove=True, scope="Environment_Variables", ) - self.parent.group_vars.remove(self) + + def __validate(self, *_args): + self.__valid_name = GtkUtils.validate_entry( + self, lambda var_name: not var_name == "WINEDLLOVERRIDES" + ) @Gtk.Template(resource_path="/com/usebottles/bottles/dialog-env-vars.ui") @@ -76,7 +95,7 @@ class EnvVarsDialog(Adw.Window): __gtype_name__ = "EnvVarsDialog" # region Widgets - entry_name = Gtk.Template.Child() + entry_new_var = Gtk.Template.Child() group_vars = Gtk.Template.Child() # endregion @@ -92,40 +111,36 @@ def __init__(self, window, config, **kwargs): self.__populate_vars_list() # connect signals - self.entry_name.connect("changed", self.__validate) - self.entry_name.connect("apply", self.__save_var) + self.entry_new_var.connect("changed", self.__validate) + self.entry_new_var.connect("apply", self.__save_var) def __validate(self, *_args): self.__valid_name = GtkUtils.validate_entry( - self.entry_name, lambda envvar: envvar.startswith("WINEDLLOVERRIDES") + self.entry_new_var, lambda var_name: not var_name == "WINEDLLOVERRIDES" ) + if not self.entry_new_var.get_text(): + self.entry_new_var.remove_css_class("error") + def __save_var(self, *_args): """ This function save the new env var to the bottle configuration """ if not self.__valid_name: - self.entry_name.set_text("") - self.entry_name.remove_css_class("error") - self.__valid_name = True return - env_name = self.entry_name.get_text() - env_value = "value" - split_value = env_name.split("=", 1) - if len(split_value) == 2: - env_name = split_value[0] - env_value = split_value[1] + new_name, new_value = ShUtils.split_assignment(self.entry_new_var.get_text()) self.manager.update_config( config=self.config, - key=env_name, - value=env_value, + key=new_name, + value=new_value, scope="Environment_Variables", ) - _entry = EnvVarEntry(parent=self, env=[env_name, env_value]) - GLib.idle_add(self.group_vars.add, _entry) - self.entry_name.set_text("") + _entry = EnvVarEntry(parent=self, env=(new_name, new_value)) + self.group_vars.set_description() + self.group_vars.add(_entry) + self.entry_new_var.set_text("") def __populate_vars_list(self): """ @@ -134,10 +149,9 @@ def __populate_vars_list(self): """ envs = self.config.Environment_Variables.items() if len(envs) == 0: - self.group_vars.set_description(_("No environment variables defined.")) + self.group_vars.set_description(_("No environment variables defined")) return - self.group_vars.set_description("") for env in envs: _entry = EnvVarEntry(parent=self, env=env) - GLib.idle_add(self.group_vars.add, _entry) + self.group_vars.add(_entry)