Add environment management for projects with variable support

This commit is contained in:
vesp 2025-12-30 12:37:13 +01:00
parent 26970cc392
commit 24b1db4b9b
14 changed files with 786 additions and 7 deletions

137
src/environments-dialog.ui Normal file
View File

@ -0,0 +1,137 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<requires lib="gtk" version="4.0"/>
<requires lib="libadwaita" version="1.0"/>
<template class="EnvironmentsDialog" parent="AdwDialog">
<property name="title">Manage Environments</property>
<property name="content-width">700</property>
<property name="content-height">500</property>
<child>
<object class="AdwToolbarView">
<child type="top">
<object class="AdwHeaderBar">
<property name="show-title">true</property>
</object>
</child>
<property name="content">
<object class="GtkScrolledWindow">
<property name="vexpand">true</property>
<child>
<object class="GtkBox">
<property name="orientation">vertical</property>
<property name="margin-start">12</property>
<property name="margin-end">12</property>
<property name="margin-top">12</property>
<property name="margin-bottom">12</property>
<property name="spacing">24</property>
<!-- Variables Section -->
<child>
<object class="GtkBox">
<property name="orientation">vertical</property>
<property name="spacing">12</property>
<child>
<object class="GtkBox">
<property name="orientation">horizontal</property>
<property name="spacing">12</property>
<child>
<object class="GtkLabel">
<property name="label">Variables</property>
<property name="xalign">0</property>
<property name="hexpand">True</property>
<style>
<class name="title-4"/>
</style>
</object>
</child>
<child>
<object class="GtkButton" id="add_variable_button">
<property name="icon-name">list-add-symbolic</property>
<property name="tooltip-text">Add variable</property>
<signal name="clicked" handler="on_add_variable_clicked"/>
<style>
<class name="flat"/>
</style>
</object>
</child>
</object>
</child>
<child>
<object class="GtkFrame">
<child>
<object class="GtkListBox" id="variables_listbox">
<property name="selection-mode">none</property>
<style>
<class name="boxed-list"/>
</style>
</object>
</child>
</object>
</child>
</object>
</child>
<!-- Environments Section -->
<child>
<object class="GtkBox">
<property name="orientation">vertical</property>
<property name="spacing">12</property>
<child>
<object class="GtkBox">
<property name="orientation">horizontal</property>
<property name="spacing">12</property>
<child>
<object class="GtkLabel">
<property name="label">Environments</property>
<property name="xalign">0</property>
<property name="hexpand">True</property>
<style>
<class name="title-4"/>
</style>
</object>
</child>
<child>
<object class="GtkButton" id="add_environment_button">
<property name="icon-name">list-add-symbolic</property>
<property name="tooltip-text">Add environment</property>
<signal name="clicked" handler="on_add_environment_clicked"/>
<style>
<class name="flat"/>
</style>
</object>
</child>
</object>
</child>
<child>
<object class="GtkFrame">
<child>
<object class="GtkListBox" id="environments_listbox">
<property name="selection-mode">none</property>
<style>
<class name="boxed-list"/>
</style>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</property>
</object>
</child>
</template>
</interface>

233
src/environments_dialog.py Normal file
View File

@ -0,0 +1,233 @@
# environments_dialog.py
#
# Copyright 2025 Pavel Baksy
#
# 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, either version 3 of the License, or
# (at your option) any later version.
#
# 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 <https://www.gnu.org/licenses/>.
#
# SPDX-License-Identifier: GPL-3.0-or-later
from gi.repository import Adw, Gtk, GObject
from .widgets.variable_row import VariableRow
from .widgets.environment_row import EnvironmentRow
@Gtk.Template(resource_path='/cz/vesp/roster/environments-dialog.ui')
class EnvironmentsDialog(Adw.Dialog):
"""Dialog for managing project environments and variables."""
__gtype_name__ = 'EnvironmentsDialog'
variables_listbox = Gtk.Template.Child()
add_variable_button = Gtk.Template.Child()
environments_listbox = Gtk.Template.Child()
add_environment_button = Gtk.Template.Child()
__gsignals__ = {
'environments-updated': (GObject.SIGNAL_RUN_FIRST, None, ()),
}
def __init__(self, project, project_manager):
super().__init__()
self.project = project
self.project_manager = project_manager
self._populate_variables()
self._populate_environments()
def _populate_variables(self):
"""Populate variables list."""
# Clear existing
while child := self.variables_listbox.get_first_child():
self.variables_listbox.remove(child)
# Add variables
for var_name in self.project.variable_names:
row = VariableRow(var_name)
row.connect('remove-requested', self._on_variable_remove, var_name)
row.connect('changed', self._on_variable_changed, var_name, row)
self.variables_listbox.append(row)
def _populate_environments(self):
"""Populate environments list."""
# Clear existing
while child := self.environments_listbox.get_first_child():
self.environments_listbox.remove(child)
# Add environments
for env in self.project.environments:
row = EnvironmentRow(env, self.project.variable_names)
row.connect('edit-requested', self._on_environment_edit, env)
row.connect('delete-requested', self._on_environment_delete, env)
row.connect('value-changed', self._on_environment_value_changed, env)
self.environments_listbox.append(row)
@Gtk.Template.Callback()
def on_add_variable_clicked(self, button):
"""Add new variable."""
dialog = Adw.AlertDialog()
dialog.set_heading("New Variable")
dialog.set_body("Enter variable name:")
entry = Gtk.Entry()
entry.set_placeholder_text("Variable name")
dialog.set_extra_child(entry)
dialog.add_response("cancel", "Cancel")
dialog.add_response("add", "Add")
dialog.set_response_appearance("add", Adw.ResponseAppearance.SUGGESTED)
dialog.set_default_response("add")
dialog.set_close_response("cancel")
def on_response(dlg, response):
if response == "add":
name = entry.get_text().strip()
if name and name not in self.project.variable_names:
self.project_manager.add_variable(self.project.id, name)
# Reload project data
self._reload_project()
self._populate_variables()
self._populate_environments()
self.emit('environments-updated')
dialog.connect("response", on_response)
dialog.present(self)
def _on_variable_remove(self, widget, var_name):
"""Remove variable with confirmation."""
dialog = Adw.AlertDialog()
dialog.set_heading("Delete Variable?")
dialog.set_body(f"Delete variable '{var_name}' from all environments? This cannot be undone.")
dialog.add_response("cancel", "Cancel")
dialog.add_response("delete", "Delete")
dialog.set_response_appearance("delete", Adw.ResponseAppearance.DESTRUCTIVE)
dialog.set_close_response("cancel")
def on_response(dlg, response):
if response == "delete":
self.project_manager.delete_variable(self.project.id, var_name)
self._reload_project()
self._populate_variables()
self._populate_environments()
self.emit('environments-updated')
dialog.connect("response", on_response)
dialog.present(self)
def _on_variable_changed(self, widget, old_name, row):
"""Handle variable name change."""
new_name = row.get_variable_name()
if new_name and new_name != old_name and new_name not in self.project.variable_names:
self.project_manager.rename_variable(self.project.id, old_name, new_name)
self._reload_project()
self._populate_variables()
self._populate_environments()
self.emit('environments-updated')
@Gtk.Template.Callback()
def on_add_environment_clicked(self, button):
"""Add new environment."""
dialog = Adw.AlertDialog()
dialog.set_heading("New Environment")
dialog.set_body("Enter environment name:")
entry = Gtk.Entry()
entry.set_placeholder_text("Environment name (e.g., Production)")
dialog.set_extra_child(entry)
dialog.add_response("cancel", "Cancel")
dialog.add_response("add", "Add")
dialog.set_response_appearance("add", Adw.ResponseAppearance.SUGGESTED)
dialog.set_default_response("add")
dialog.set_close_response("cancel")
def on_response(dlg, response):
if response == "add":
name = entry.get_text().strip()
if name:
self.project_manager.add_environment(self.project.id, name)
self._reload_project()
self._populate_environments()
self.emit('environments-updated')
dialog.connect("response", on_response)
dialog.present(self)
def _on_environment_edit(self, widget, environment):
"""Edit environment name."""
dialog = Adw.AlertDialog()
dialog.set_heading("Edit Environment")
dialog.set_body("Enter new name:")
entry = Gtk.Entry()
entry.set_text(environment.name)
dialog.set_extra_child(entry)
dialog.add_response("cancel", "Cancel")
dialog.add_response("save", "Save")
dialog.set_response_appearance("save", Adw.ResponseAppearance.SUGGESTED)
def on_response(dlg, response):
if response == "save":
new_name = entry.get_text().strip()
if new_name:
self.project_manager.update_environment(self.project.id, environment.id, name=new_name)
self._reload_project()
self._populate_environments()
self.emit('environments-updated')
dialog.connect("response", on_response)
dialog.present(self)
def _on_environment_delete(self, widget, environment):
"""Delete environment with confirmation."""
# Prevent deletion of last environment
if len(self.project.environments) <= 1:
dialog = Adw.AlertDialog()
dialog.set_heading("Cannot Delete")
dialog.set_body("Each project must have at least one environment.")
dialog.add_response("ok", "OK")
dialog.present(self)
return
dialog = Adw.AlertDialog()
dialog.set_heading("Delete Environment?")
dialog.set_body(f"Delete environment '{environment.name}'? This cannot be undone.")
dialog.add_response("cancel", "Cancel")
dialog.add_response("delete", "Delete")
dialog.set_response_appearance("delete", Adw.ResponseAppearance.DESTRUCTIVE)
dialog.set_close_response("cancel")
def on_response(dlg, response):
if response == "delete":
self.project_manager.delete_environment(self.project.id, environment.id)
self._reload_project()
self._populate_environments()
self.emit('environments-updated')
dialog.connect("response", on_response)
dialog.present(self)
def _on_environment_value_changed(self, widget, var_name, value, environment):
"""Handle environment variable value change."""
self.project_manager.update_environment_variable(self.project.id, environment.id, var_name, value)
self.emit('environments-updated')
def _reload_project(self):
"""Reload project data from manager."""
projects = self.project_manager.load_projects()
for p in projects:
if p.id == self.project.id:
self.project = p
break

View File

@ -37,6 +37,7 @@ roster_sources = [
'tab_manager.py',
'constants.py',
'icon_picker_dialog.py',
'environments_dialog.py',
'preferences_dialog.py',
'request_tab_widget.py',
]
@ -50,6 +51,8 @@ widgets_sources = [
'widgets/history_item.py',
'widgets/project_item.py',
'widgets/request_item.py',
'widgets/variable_row.py',
'widgets/environment_row.py',
]
install_data(widgets_sources, install_dir: moduledir / 'widgets')

View File

@ -121,14 +121,51 @@ class SavedRequest:
)
@dataclass
class Environment:
"""An environment with variable values."""
id: str # UUID
name: str # e.g., "production", "integration", "stage"
variables: Dict[str, str] # variable_name -> value
created_at: str # ISO format
def to_dict(self):
"""Convert to dictionary for JSON serialization."""
return {
'id': self.id,
'name': self.name,
'variables': self.variables,
'created_at': self.created_at
}
@classmethod
def from_dict(cls, data):
"""Create instance from dictionary."""
return cls(
id=data['id'],
name=data['name'],
variables=data.get('variables', {}),
created_at=data['created_at']
)
@dataclass
class Project:
"""A project containing saved requests."""
"""A project containing saved requests and environments."""
id: str # UUID
name: str
requests: List[SavedRequest]
created_at: str # ISO format
icon: str = "folder-symbolic" # Icon name
variable_names: List[str] = None # List of variable names defined for this project
environments: List[Environment] = None # List of environments
def __post_init__(self):
"""Initialize optional fields."""
if self.variable_names is None:
self.variable_names = []
if self.environments is None:
self.environments = []
def to_dict(self):
"""Convert to dictionary for JSON serialization."""
@ -137,7 +174,9 @@ class Project:
'name': self.name,
'requests': [req.to_dict() for req in self.requests],
'created_at': self.created_at,
'icon': self.icon
'icon': self.icon,
'variable_names': self.variable_names,
'environments': [env.to_dict() for env in self.environments]
}
@classmethod
@ -148,7 +187,9 @@ class Project:
name=data['name'],
requests=[SavedRequest.from_dict(r) for r in data.get('requests', [])],
created_at=data['created_at'],
icon=data.get('icon', 'folder-symbolic') # Default for old data
icon=data.get('icon', 'folder-symbolic'), # Default for old data
variable_names=data.get('variable_names', []),
environments=[Environment.from_dict(e) for e in data.get('environments', [])]
)

View File

@ -25,7 +25,7 @@ from datetime import datetime, timezone
import gi
gi.require_version('GLib', '2.0')
from gi.repository import GLib
from .models import Project, SavedRequest, HttpRequest
from .models import Project, SavedRequest, HttpRequest, Environment
class ProjectManager:
@ -69,13 +69,25 @@ class ProjectManager:
print(f"Error saving projects: {e}")
def add_project(self, name: str) -> Project:
"""Create new project."""
"""Create new project with default environment."""
projects = self.load_projects()
now = datetime.now(timezone.utc).isoformat()
# Create default environment
default_env = Environment(
id=str(uuid.uuid4()),
name="Default",
variables={},
created_at=now
)
project = Project(
id=str(uuid.uuid4()),
name=name,
requests=[],
created_at=datetime.now(timezone.utc).isoformat()
created_at=now,
variable_names=[],
environments=[default_env]
)
projects.append(project)
self.save_projects(projects)
@ -154,3 +166,104 @@ class ProjectManager:
p.requests = [r for r in p.requests if r.id != request_id]
break
self.save_projects(projects)
def add_environment(self, project_id: str, name: str) -> Environment:
"""Add environment to a project."""
projects = self.load_projects()
now = datetime.now(timezone.utc).isoformat()
environment = None
for p in projects:
if p.id == project_id:
# Create environment with empty values for all variables
variables = {var_name: "" for var_name in p.variable_names}
environment = Environment(
id=str(uuid.uuid4()),
name=name,
variables=variables,
created_at=now
)
p.environments.append(environment)
break
self.save_projects(projects)
return environment
def update_environment(self, project_id: str, env_id: str, name: str = None, variables: dict = None):
"""Update an environment's name and/or variables."""
projects = self.load_projects()
for p in projects:
if p.id == project_id:
for env in p.environments:
if env.id == env_id:
if name is not None:
env.name = name
if variables is not None:
env.variables = variables
break
break
self.save_projects(projects)
def delete_environment(self, project_id: str, env_id: str):
"""Delete an environment."""
projects = self.load_projects()
for p in projects:
if p.id == project_id:
p.environments = [e for e in p.environments if e.id != env_id]
break
self.save_projects(projects)
def add_variable(self, project_id: str, variable_name: str):
"""Add a variable to the project and all environments."""
projects = self.load_projects()
for p in projects:
if p.id == project_id:
if variable_name not in p.variable_names:
p.variable_names.append(variable_name)
# Add empty value to all environments
for env in p.environments:
env.variables[variable_name] = ""
break
self.save_projects(projects)
def rename_variable(self, project_id: str, old_name: str, new_name: str):
"""Rename a variable in the project and all environments."""
projects = self.load_projects()
for p in projects:
if p.id == project_id:
# Update variable_names list
if old_name in p.variable_names:
idx = p.variable_names.index(old_name)
p.variable_names[idx] = new_name
# Update all environments
for env in p.environments:
if old_name in env.variables:
env.variables[new_name] = env.variables.pop(old_name)
break
self.save_projects(projects)
def delete_variable(self, project_id: str, variable_name: str):
"""Delete a variable from the project and all environments."""
projects = self.load_projects()
for p in projects:
if p.id == project_id:
# Remove from variable_names
if variable_name in p.variable_names:
p.variable_names.remove(variable_name)
# Remove from all environments
for env in p.environments:
env.variables.pop(variable_name, None)
break
self.save_projects(projects)
def update_environment_variable(self, project_id: str, env_id: str, variable_name: str, value: str):
"""Update a single variable value in an environment."""
projects = self.load_projects()
for p in projects:
if p.id == project_id:
for env in p.environments:
if env.id == env_id:
env.variables[variable_name] = value
break
break
self.save_projects(projects)

View File

@ -5,9 +5,12 @@
<file preprocess="xml-stripblanks">shortcuts-dialog.ui</file>
<file preprocess="xml-stripblanks">preferences-dialog.ui</file>
<file preprocess="xml-stripblanks">icon-picker-dialog.ui</file>
<file preprocess="xml-stripblanks">environments-dialog.ui</file>
<file preprocess="xml-stripblanks">widgets/header-row.ui</file>
<file preprocess="xml-stripblanks">widgets/history-item.ui</file>
<file preprocess="xml-stripblanks">widgets/project-item.ui</file>
<file preprocess="xml-stripblanks">widgets/request-item.ui</file>
<file preprocess="xml-stripblanks">widgets/variable-row.ui</file>
<file preprocess="xml-stripblanks">widgets/environment-row.ui</file>
</gresource>
</gresources>

View File

@ -21,5 +21,7 @@ from .header_row import HeaderRow
from .history_item import HistoryItem
from .project_item import ProjectItem
from .request_item import RequestItem
from .variable_row import VariableRow
from .environment_row import EnvironmentRow
__all__ = ['HeaderRow', 'HistoryItem', 'ProjectItem', 'RequestItem']
__all__ = ['HeaderRow', 'HistoryItem', 'ProjectItem', 'RequestItem', 'VariableRow', 'EnvironmentRow']

View File

@ -0,0 +1,52 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<requires lib="gtk" version="4.0"/>
<template class="EnvironmentRow" parent="GtkBox">
<property name="orientation">horizontal</property>
<property name="spacing">12</property>
<property name="margin-start">12</property>
<property name="margin-end">12</property>
<property name="margin-top">6</property>
<property name="margin-bottom">6</property>
<child>
<object class="GtkLabel" id="name_label">
<property name="xalign">0</property>
<property name="width-chars">15</property>
<style>
<class name="heading"/>
</style>
</object>
</child>
<child>
<object class="GtkBox" id="values_box">
<property name="orientation">horizontal</property>
<property name="spacing">6</property>
<property name="hexpand">True</property>
</object>
</child>
<child>
<object class="GtkButton" id="edit_button">
<property name="icon-name">document-edit-symbolic</property>
<property name="tooltip-text">Edit environment</property>
<signal name="clicked" handler="on_edit_clicked"/>
<style>
<class name="flat"/>
</style>
</object>
</child>
<child>
<object class="GtkButton" id="delete_button">
<property name="icon-name">edit-delete-symbolic</property>
<property name="tooltip-text">Delete environment</property>
<signal name="clicked" handler="on_delete_clicked"/>
<style>
<class name="flat"/>
</style>
</object>
</child>
</template>
</interface>

View File

@ -0,0 +1,86 @@
# environment_row.py
#
# Copyright 2025 Pavel Baksy
#
# 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, either version 3 of the License, or
# (at your option) any later version.
#
# 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 <https://www.gnu.org/licenses/>.
#
# SPDX-License-Identifier: GPL-3.0-or-later
from gi.repository import Gtk, GObject
@Gtk.Template(resource_path='/cz/vesp/roster/widgets/environment-row.ui')
class EnvironmentRow(Gtk.Box):
"""Widget for displaying and editing an environment with its variables."""
__gtype_name__ = 'EnvironmentRow'
name_label = Gtk.Template.Child()
values_box = Gtk.Template.Child()
edit_button = Gtk.Template.Child()
delete_button = Gtk.Template.Child()
__gsignals__ = {
'edit-requested': (GObject.SIGNAL_RUN_FIRST, None, ()),
'delete-requested': (GObject.SIGNAL_RUN_FIRST, None, ()),
'value-changed': (GObject.SIGNAL_RUN_FIRST, None, (str, str)) # variable_name, value
}
def __init__(self, environment, variable_names):
super().__init__()
self.environment = environment
self.variable_names = variable_names
self.value_entries = {}
self._populate()
def _populate(self):
"""Populate with environment data."""
self.name_label.set_text(self.environment.name)
# Clear values box
while child := self.values_box.get_first_child():
self.values_box.remove(child)
# Create entry for each variable
for var_name in self.variable_names:
entry = Gtk.Entry()
entry.set_placeholder_text(var_name)
entry.set_text(self.environment.variables.get(var_name, ""))
entry.set_hexpand(True)
entry.connect('changed', self._on_value_changed, var_name)
self.values_box.append(entry)
self.value_entries[var_name] = entry
def _on_value_changed(self, entry, var_name):
"""Handle value entry changes."""
self.emit('value-changed', var_name, entry.get_text())
@Gtk.Template.Callback()
def on_edit_clicked(self, button):
"""Handle edit button click."""
self.emit('edit-requested')
@Gtk.Template.Callback()
def on_delete_clicked(self, button):
"""Handle delete button click."""
self.emit('delete-requested')
def refresh(self, variable_names):
"""Refresh widget with updated variable names."""
self.variable_names = variable_names
self._populate()
def get_values(self):
"""Get all variable values."""
return {var_name: entry.get_text() for var_name, entry in self.value_entries.items()}

View File

@ -5,6 +5,10 @@
<menu id="project_menu">
<section>
<item>
<attribute name="label">Manage Environments</attribute>
<attribute name="action">project.manage-environments</attribute>
</item>
<item>
<attribute name="label">Add Request</attribute>
<attribute name="action">project.add-request</attribute>

View File

@ -38,6 +38,7 @@ class ProjectItem(Gtk.Box):
'add-request-requested': (GObject.SIGNAL_RUN_FIRST, None, ()),
'request-load-requested': (GObject.SIGNAL_RUN_FIRST, None, (object,)),
'request-delete-requested': (GObject.SIGNAL_RUN_FIRST, None, (object,)),
'manage-environments-requested': (GObject.SIGNAL_RUN_FIRST, None, ()),
}
def __init__(self, project):
@ -56,6 +57,10 @@ class ProjectItem(Gtk.Box):
"""Setup action group for menu."""
actions = Gio.SimpleActionGroup.new()
action = Gio.SimpleAction.new("manage-environments", None)
action.connect("activate", lambda *_: self.emit('manage-environments-requested'))
actions.add_action(action)
action = Gio.SimpleAction.new("add-request", None)
action.connect("activate", lambda *_: self.emit('add-request-requested'))
actions.add_action(action)

View File

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<requires lib="gtk" version="4.0"/>
<template class="VariableRow" parent="GtkBox">
<property name="orientation">horizontal</property>
<property name="spacing">6</property>
<property name="margin-start">6</property>
<property name="margin-end">6</property>
<property name="margin-top">3</property>
<property name="margin-bottom">3</property>
<child>
<object class="GtkEntry" id="name_entry">
<property name="placeholder-text">Variable name</property>
<property name="hexpand">True</property>
</object>
</child>
<child>
<object class="GtkButton" id="remove_button">
<property name="icon-name">edit-delete-symbolic</property>
<property name="tooltip-text">Remove variable</property>
<signal name="clicked" handler="on_remove_clicked" swapped="no"/>
<style>
<class name="flat"/>
</style>
</object>
</child>
</template>
</interface>

View File

@ -0,0 +1,57 @@
# variable_row.py
#
# Copyright 2025 Pavel Baksy
#
# 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, either version 3 of the License, or
# (at your option) any later version.
#
# 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 <https://www.gnu.org/licenses/>.
#
# SPDX-License-Identifier: GPL-3.0-or-later
from gi.repository import Gtk, GObject
@Gtk.Template(resource_path='/cz/vesp/roster/widgets/variable-row.ui')
class VariableRow(Gtk.Box):
"""Widget for editing a variable name."""
__gtype_name__ = 'VariableRow'
name_entry = Gtk.Template.Child()
remove_button = Gtk.Template.Child()
__gsignals__ = {
'remove-requested': (GObject.SIGNAL_RUN_FIRST, None, ()),
'changed': (GObject.SIGNAL_RUN_FIRST, None, ())
}
def __init__(self, variable_name=""):
super().__init__()
self.name_entry.set_text(variable_name)
self.name_entry.connect('changed', self._on_entry_changed)
def _on_entry_changed(self, entry):
"""Handle changes to name entry."""
self.emit('changed')
@Gtk.Template.Callback()
def on_remove_clicked(self, button):
"""Handle remove button click."""
self.emit('remove-requested')
def get_variable_name(self):
"""Return variable name."""
return self.name_entry.get_text().strip()
def set_variable_name(self, name: str):
"""Set variable name."""
self.name_entry.set_text(name)

View File

@ -26,6 +26,7 @@ from .history_manager import HistoryManager
from .project_manager import ProjectManager
from .tab_manager import TabManager
from .icon_picker_dialog import IconPickerDialog
from .environments_dialog import EnvironmentsDialog
from .request_tab_widget import RequestTabWidget
from .widgets.history_item import HistoryItem
from .widgets.project_item import ProjectItem
@ -528,6 +529,7 @@ class RosterWindow(Adw.ApplicationWindow):
item.connect('add-request-requested', self._on_add_to_project, project)
item.connect('request-load-requested', self._on_load_request)
item.connect('request-delete-requested', self._on_delete_request, project)
item.connect('manage-environments-requested', self._on_manage_environments, project)
self.projects_listbox.append(item)
@Gtk.Template.Callback()
@ -628,6 +630,17 @@ class RosterWindow(Adw.ApplicationWindow):
dialog.connect("response", on_response)
dialog.present(self)
def _on_manage_environments(self, widget, project):
"""Show environments management dialog."""
dialog = EnvironmentsDialog(project, self.project_manager)
def on_environments_updated(dlg):
# Reload projects to reflect changes
self._load_projects()
dialog.connect('environments-updated', on_environments_updated)
dialog.present(self)
@Gtk.Template.Callback()
def on_save_request_clicked(self, button):
"""Save current request to a project."""