From 35c77c58016f436b7b1b9646c5b72fdbdf2de7e5 Mon Sep 17 00:00:00 2001 From: Jonatas Esteves Date: Mon, 6 Jan 2025 21:27:13 -0300 Subject: [PATCH 1/8] 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 | 14 +++-- bottles/frontend/utils/meson.build | 1 + bottles/frontend/utils/sh.py | 31 +++++++++++ bottles/frontend/windows/envvars.py | 68 +++++++++++++++---------- 6 files changed, 83 insertions(+), 42 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..c8116134d06 100644 --- a/bottles/frontend/utils/gtk.py +++ b/bottles/frontend/utils/gtk.py @@ -15,28 +15,26 @@ # 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() - if ( - re.search("[@!#$%^&*()<>?/|}{~:.;,'\"]", text) - or len(text) == 0 - or text.isspace() - ): + var_assignment = entry.get_text() + var_name = ShUtils.split_assignment(var_assignment)[0] + if "=" 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..42c6bf73e8b --- /dev/null +++ b/bottles/frontend/utils/sh.py @@ -0,0 +1,31 @@ +# 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) From ea9e450746c58c83fb0929f59b9f4b916ee0b4da Mon Sep 17 00:00:00 2001 From: Jonatas Esteves Date: Tue, 7 Jan 2025 18:32:36 -0300 Subject: [PATCH 2/8] envvars-dialog: Port to AdwDialog --- bottles/frontend/ui/dialog-env-vars.blp | 21 +++++++-------------- bottles/frontend/windows/envvars.py | 6 ++++-- 2 files changed, 11 insertions(+), 16 deletions(-) diff --git a/bottles/frontend/ui/dialog-env-vars.blp b/bottles/frontend/ui/dialog-env-vars.blp index bace43767ee..f7194ced609 100644 --- a/bottles/frontend/ui/dialog-env-vars.blp +++ b/bottles/frontend/ui/dialog-env-vars.blp @@ -1,25 +1,18 @@ using Gtk 4.0; using Adw 1; -template $EnvVarsDialog: Adw.Window { - modal: true; - default-width: 500; - default-height: 500; - - ShortcutController { - Shortcut { - trigger: "Escape"; - action: "action(window.close)"; - } - } +template $EnvVarsDialog: Adw.Dialog { + content-width: 600; + content-height: 800; + title: _("Environment Variables"); Box { orientation: vertical; Adw.HeaderBar { - title-widget: Adw.WindowTitle { - title: _("Environment Variables"); - }; + styles [ + "flat", + ] } Adw.PreferencesPage { diff --git a/bottles/frontend/windows/envvars.py b/bottles/frontend/windows/envvars.py index 9f33210baf0..736c77afbdd 100644 --- a/bottles/frontend/windows/envvars.py +++ b/bottles/frontend/windows/envvars.py @@ -91,7 +91,7 @@ def __validate(self, *_args): @Gtk.Template(resource_path="/com/usebottles/bottles/dialog-env-vars.ui") -class EnvVarsDialog(Adw.Window): +class EnvVarsDialog(Adw.Dialog): __gtype_name__ = "EnvVarsDialog" # region Widgets @@ -101,7 +101,6 @@ class EnvVarsDialog(Adw.Window): def __init__(self, window, config, **kwargs): super().__init__(**kwargs) - self.set_transient_for(window) # common variables and references self.window = window @@ -114,6 +113,9 @@ def __init__(self, window, config, **kwargs): self.entry_new_var.connect("changed", self.__validate) self.entry_new_var.connect("apply", self.__save_var) + def present(self): + return super().present(self.window) + def __validate(self, *_args): self.__valid_name = GtkUtils.validate_entry( self.entry_new_var, lambda var_name: not var_name == "WINEDLLOVERRIDES" From 8bba5d05231104edb01051836a50a80fc0741918 Mon Sep 17 00:00:00 2001 From: Jonatas Esteves Date: Tue, 7 Jan 2025 23:32:17 -0300 Subject: [PATCH 3/8] envvars-dialog: Add tooltip to Remove button --- bottles/frontend/ui/env-var-entry.blp | 1 + 1 file changed, 1 insertion(+) diff --git a/bottles/frontend/ui/env-var-entry.blp b/bottles/frontend/ui/env-var-entry.blp index 8e4afe55d21..8ab07ecdba3 100644 --- a/bottles/frontend/ui/env-var-entry.blp +++ b/bottles/frontend/ui/env-var-entry.blp @@ -7,6 +7,7 @@ template $EnvVarEntry: Adw.EntryRow { Button btn_remove { valign: center; icon-name: "user-trash-symbolic"; + tooltip-text: _("Remove"); styles [ "flat", From 56f959d8bd502e8ebb7a2717b341e2842da60589 Mon Sep 17 00:00:00 2001 From: Jonatas Esteves Date: Tue, 7 Jan 2025 23:47:04 -0300 Subject: [PATCH 4/8] envvars-dialog: Center text entry vertically Traverse AdwEntryRow's widget tree to remove visibility of the title labels and change alignment of the text entry to center. This implementation is tightly coupled with the internal representation of AdwEntryRow. Checks were added to prevent crashing if that representation changes, but in such case the layout may get askew. --- bottles/frontend/windows/envvars.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/bottles/frontend/windows/envvars.py b/bottles/frontend/windows/envvars.py index 736c77afbdd..47f9990efb8 100644 --- a/bottles/frontend/windows/envvars.py +++ b/bottles/frontend/windows/envvars.py @@ -17,9 +17,12 @@ from gi.repository import Gtk, GLib, Adw +from bottles.backend.logger import Logger from bottles.frontend.utils.gtk import GtkUtils from bottles.frontend.utils.sh import ShUtils +logging = Logger() + @Gtk.Template(resource_path="/com/usebottles/bottles/env-var-entry.ui") class EnvVarEntry(Adw.EntryRow): @@ -44,6 +47,23 @@ def __init__(self, parent, env, **kwargs): self.connect("apply", self.__save) self.btn_remove.connect("clicked", self.__remove) + try: + widget = ( + self.get_child().get_first_child().get_next_sibling().get_first_child() + ) + while isinstance(widget, Gtk.Label): + widget.set_visible(False) + widget = widget.get_next_sibling() + + if isinstance(widget, Gtk.Text): + widget.set_valign(Gtk.Align.CENTER) + else: + raise RuntimeError("Could not find widget Gtk.Text") + except Exception as e: + logging.error( + f"{type(e)}: {e}\nEnvVarEntry could not find text widget. Did AdwEntryRow change it's widget tree?" + ) + def __save(self, *_args): """ Change the env var value according to the From 00cd72961da7a2f38ee5dbc592dfe017120c1344 Mon Sep 17 00:00:00 2001 From: Jonatas Esteves Date: Thu, 9 Jan 2025 00:33:33 -0300 Subject: [PATCH 5/8] envvars-dialog: Rename classes to more descriptive names --- bottles/frontend/ui/dialog-env-vars.blp | 2 +- bottles/frontend/ui/env-var-entry.blp | 2 +- bottles/frontend/views/bottle_preferences.py | 4 ++-- bottles/frontend/windows/envvars.py | 14 +++++++------- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/bottles/frontend/ui/dialog-env-vars.blp b/bottles/frontend/ui/dialog-env-vars.blp index f7194ced609..b33731fbafb 100644 --- a/bottles/frontend/ui/dialog-env-vars.blp +++ b/bottles/frontend/ui/dialog-env-vars.blp @@ -1,7 +1,7 @@ using Gtk 4.0; using Adw 1; -template $EnvVarsDialog: Adw.Dialog { +template $EnvironmentVariablesDialog: Adw.Dialog { content-width: 600; content-height: 800; title: _("Environment Variables"); diff --git a/bottles/frontend/ui/env-var-entry.blp b/bottles/frontend/ui/env-var-entry.blp index 8ab07ecdba3..ef0fcff0b9d 100644 --- a/bottles/frontend/ui/env-var-entry.blp +++ b/bottles/frontend/ui/env-var-entry.blp @@ -1,7 +1,7 @@ using Gtk 4.0; using Adw 1; -template $EnvVarEntry: Adw.EntryRow { +template $EnvironmentVariableEntryRow: Adw.EntryRow { show-apply-button: true; Button btn_remove { diff --git a/bottles/frontend/views/bottle_preferences.py b/bottles/frontend/views/bottle_preferences.py index 1eb21894e94..754576baed7 100644 --- a/bottles/frontend/views/bottle_preferences.py +++ b/bottles/frontend/views/bottle_preferences.py @@ -46,7 +46,7 @@ from bottles.frontend.windows.display import DisplayDialog from bottles.frontend.windows.dlloverrides import DLLOverridesDialog from bottles.frontend.windows.drives import DrivesDialog -from bottles.frontend.windows.envvars import EnvVarsDialog +from bottles.frontend.windows.envvars import EnvironmentVariablesDialog from bottles.frontend.windows.exclusionpatterns import ExclusionPatternsDialog from bottles.frontend.windows.fsr import FsrDialog from bottles.frontend.windows.gamescope import GamescopeDialog @@ -200,7 +200,7 @@ def __init__(self, details, config, **kwargs): "activated", self.__show_feature_dialog, DLLOverridesDialog ) self.row_env_variables.connect( - "activated", self.__show_feature_dialog, EnvVarsDialog + "activated", self.__show_feature_dialog, EnvironmentVariablesDialog ) self.row_drives.connect("activated", self.__show_feature_dialog, DrivesDialog) self.btn_manage_gamescope.connect( diff --git a/bottles/frontend/windows/envvars.py b/bottles/frontend/windows/envvars.py index 47f9990efb8..8a0161cbac4 100644 --- a/bottles/frontend/windows/envvars.py +++ b/bottles/frontend/windows/envvars.py @@ -25,8 +25,8 @@ @Gtk.Template(resource_path="/com/usebottles/bottles/env-var-entry.ui") -class EnvVarEntry(Adw.EntryRow): - __gtype_name__ = "EnvVarEntry" +class EnvironmentVariableEntryRow(Adw.EntryRow): + __gtype_name__ = "EnvironmentVariableEntryRow" # region Widgets btn_remove = Gtk.Template.Child() @@ -61,7 +61,7 @@ def __init__(self, parent, env, **kwargs): raise RuntimeError("Could not find widget Gtk.Text") except Exception as e: logging.error( - f"{type(e)}: {e}\nEnvVarEntry could not find text widget. Did AdwEntryRow change it's widget tree?" + f"{type(e)}: {e}\nEnvironmentVariableEntryRow could not find text widget. Did AdwEntryRow change it's widget tree?" ) def __save(self, *_args): @@ -111,8 +111,8 @@ def __validate(self, *_args): @Gtk.Template(resource_path="/com/usebottles/bottles/dialog-env-vars.ui") -class EnvVarsDialog(Adw.Dialog): - __gtype_name__ = "EnvVarsDialog" +class EnvironmentVariablesDialog(Adw.Dialog): + __gtype_name__ = "EnvironmentVariablesDialog" # region Widgets entry_new_var = Gtk.Template.Child() @@ -159,7 +159,7 @@ def __save_var(self, *_args): value=new_value, scope="Environment_Variables", ) - _entry = EnvVarEntry(parent=self, env=(new_name, new_value)) + _entry = EnvironmentVariableEntryRow(parent=self, env=(new_name, new_value)) self.group_vars.set_description() self.group_vars.add(_entry) self.entry_new_var.set_text("") @@ -175,5 +175,5 @@ def __populate_vars_list(self): return for env in envs: - _entry = EnvVarEntry(parent=self, env=env) + _entry = EnvironmentVariableEntryRow(parent=self, env=env) self.group_vars.add(_entry) From 4adc550ac4ae79932ca53f5da6dc8305349b4fbc Mon Sep 17 00:00:00 2001 From: Jonatas Esteves Date: Sat, 11 Jan 2025 13:45:53 -0300 Subject: [PATCH 6/8] envvars-dialog: Improve validation * Only show the apply button when the value is valid * Don't add error style to the text field when the value is empty or when it doesn't contain an equal sign --- bottles/frontend/utils/gtk.py | 13 ++++++++++++- bottles/frontend/windows/envvars.py | 6 +++--- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/bottles/frontend/utils/gtk.py b/bottles/frontend/utils/gtk.py index c8116134d06..e58dd376a4c 100644 --- a/bottles/frontend/utils/gtk.py +++ b/bottles/frontend/utils/gtk.py @@ -29,15 +29,26 @@ class GtkUtils: def validate_entry(entry, extend=None) -> bool: var_assignment = entry.get_text() var_name = ShUtils.split_assignment(var_assignment)[0] - if "=" not in var_assignment or not ShUtils.is_name(var_name): + if var_name and not ShUtils.is_name(var_name): + entry.set_show_apply_button(False) + entry.set_show_apply_button(True) entry.add_css_class("error") return False + if not var_name or "=" not in var_assignment: + entry.set_show_apply_button(False) + entry.set_show_apply_button(True) + entry.remove_css_class("error") + return False + if extend is not None: if not extend(var_name): + entry.set_show_apply_button(False) + entry.set_show_apply_button(True) entry.add_css_class("error") return False + entry.set_show_apply_button(True) entry.remove_css_class("error") return True diff --git a/bottles/frontend/windows/envvars.py b/bottles/frontend/windows/envvars.py index 8a0161cbac4..f44fd52bd31 100644 --- a/bottles/frontend/windows/envvars.py +++ b/bottles/frontend/windows/envvars.py @@ -109,6 +109,9 @@ def __validate(self, *_args): self, lambda var_name: not var_name == "WINEDLLOVERRIDES" ) + if not self.__valid_name: + self.add_css_class("error") + @Gtk.Template(resource_path="/com/usebottles/bottles/dialog-env-vars.ui") class EnvironmentVariablesDialog(Adw.Dialog): @@ -141,9 +144,6 @@ def __validate(self, *_args): 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 From e5db9f1ae6458f4a1aae26b1f05145335262da2e Mon Sep 17 00:00:00 2001 From: Jonatas Esteves Date: Sat, 11 Jan 2025 14:14:12 -0300 Subject: [PATCH 7/8] envvars-dialog: Always show label when list is empty --- bottles/frontend/windows/envvars.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/bottles/frontend/windows/envvars.py b/bottles/frontend/windows/envvars.py index f44fd52bd31..82c789f10b5 100644 --- a/bottles/frontend/windows/envvars.py +++ b/bottles/frontend/windows/envvars.py @@ -90,7 +90,7 @@ def __remove(self, *_args): destroy the widget """ self.__remove_config() - self.parent.group_vars.remove(self) + self.parent.remove_entry(self) def __remove_config(self, *_args): """ @@ -164,15 +164,21 @@ def __save_var(self, *_args): self.group_vars.add(_entry) self.entry_new_var.set_text("") + def remove_entry(self, _entry): + self.group_vars.remove(_entry) + self.__set_description() + + def __set_description(self): + if len(self.config.Environment_Variables.items()) == 0: + self.group_vars.set_description(_("No environment variables defined")) + def __populate_vars_list(self): """ This function populate the list of env vars with the existing ones from the bottle configuration """ envs = self.config.Environment_Variables.items() - if len(envs) == 0: - self.group_vars.set_description(_("No environment variables defined")) - return + self.__set_description() for env in envs: _entry = EnvironmentVariableEntryRow(parent=self, env=env) From 1b34de7b62016b1eb8053f45ba3299cf87579a41 Mon Sep 17 00:00:00 2001 From: Jonatas Esteves Date: Mon, 13 Jan 2025 09:27:08 -0300 Subject: [PATCH 8/8] envvars-dialog: small refactor to improve documentation * Move non-obvious code blocks into methods to better describe why it is needed * Update all comments --- bottles/frontend/utils/gtk.py | 19 +++++++++++++------ bottles/frontend/windows/envvars.py | 28 ++++++++++++++++------------ 2 files changed, 29 insertions(+), 18 deletions(-) diff --git a/bottles/frontend/utils/gtk.py b/bottles/frontend/utils/gtk.py index e58dd376a4c..0334e35c310 100644 --- a/bottles/frontend/utils/gtk.py +++ b/bottles/frontend/utils/gtk.py @@ -30,21 +30,18 @@ def validate_entry(entry, extend=None) -> bool: var_assignment = entry.get_text() var_name = ShUtils.split_assignment(var_assignment)[0] if var_name and not ShUtils.is_name(var_name): - entry.set_show_apply_button(False) - entry.set_show_apply_button(True) + GtkUtils.reset_entry_apply_button(entry) entry.add_css_class("error") return False if not var_name or "=" not in var_assignment: - entry.set_show_apply_button(False) - entry.set_show_apply_button(True) + GtkUtils.reset_entry_apply_button(entry) entry.remove_css_class("error") return False if extend is not None: if not extend(var_name): - entry.set_show_apply_button(False) - entry.set_show_apply_button(True) + GtkUtils.reset_entry_apply_button(entry) entry.add_css_class("error") return False @@ -52,6 +49,16 @@ def validate_entry(entry, extend=None) -> bool: entry.remove_css_class("error") return True + @staticmethod + def reset_entry_apply_button(entry) -> None: + """ + Reset the apply_button within AdwEntryRow to hide it without disabling + the functionality. This is needed because the widget does not provide + an API to control when the button is displayed without disabling it + """ + entry.set_show_apply_button(False) + entry.set_show_apply_button(True) + @staticmethod def run_in_main_loop(func): @wraps(func) diff --git a/bottles/frontend/windows/envvars.py b/bottles/frontend/windows/envvars.py index 82c789f10b5..ddcc0f11aab 100644 --- a/bottles/frontend/windows/envvars.py +++ b/bottles/frontend/windows/envvars.py @@ -47,6 +47,15 @@ def __init__(self, parent, env, **kwargs): self.connect("apply", self.__save) self.btn_remove.connect("clicked", self.__remove) + self.__customize_layout() + + def __customize_layout(self): + """ + Align text input field vertically. Hide unused labels and make layout + changes as needed to display the text correctly. We manually traverse + AdwEntryRow's widget tree to make these changes because it does not + offer options for these customizations on its public API + """ try: widget = ( self.get_child().get_first_child().get_next_sibling().get_first_child() @@ -66,8 +75,8 @@ def __init__(self, parent, env, **kwargs): def __save(self, *_args): """ - Change the env var value according to the - user input and update the bottle configuration + Change the environment variable value according to the user input and + update the bottle configuration """ if not self.__valid_name: return @@ -86,16 +95,14 @@ def __save(self, *_args): def __remove(self, *_args): """ - Remove the env var from the bottle configuration and + Remove the environment variable from the bottle configuration and destroy the widget """ self.__remove_config() self.parent.remove_entry(self) def __remove_config(self, *_args): - """ - Remove the env var from the bottle configuration - """ + """Remove the environment variable from the bottle configuration""" self.manager.update_config( config=self.config, key=self.env[0], @@ -145,10 +152,7 @@ def __validate(self, *_args): ) def __save_var(self, *_args): - """ - This function save the new env var to the - bottle configuration - """ + """Save the new environment variable to the bottle configuration""" if not self.__valid_name: return @@ -174,8 +178,8 @@ def __set_description(self): def __populate_vars_list(self): """ - This function populate the list of env vars - with the existing ones from the bottle configuration + Populate the list of environment variables with the existing ones from + the bottle configuration """ envs = self.config.Environment_Variables.items() self.__set_description()