libervia-backend/libervia/tui/profile_manager.py
Goffi e5ed4e3d7c tui: Q&D adaptation to new code:
use the new `quick_bridge` adapter and do few changes to make TUI work with new backend.
TUI will probably be rewritten at some point, this is just an update to keep it working.

`urwid` has been updated to have a version working with Python 3.14.
2026-11-04 18:11:04 +01:00

228 lines
8 KiB
Python

#!/usr/bin/env python3
# Libervia TUI
# Copyright (C) 2009-2026 Jérôme Poisson (goffi@goffi.org)
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero 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 Affero General Public License for more details.
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from libervia.backend.core.i18n import _
from libervia.backend.core import log as logging
log = logging.getLogger(__name__)
from libervia.frontends.quick_frontend.quick_profile_manager import QuickProfileManager
from libervia.tui.constants import Const as C
from libervia.tui.keys import action_key_map as a_key
from urwid_satext import sat_widgets
import urwid
class ProfileManager(QuickProfileManager, urwid.WidgetWrap):
def __init__(self, host, autoconnect=None):
QuickProfileManager.__init__(self, host, autoconnect)
# login & password box must be created before list because of on_profile_change
self.login_wid = sat_widgets.AdvancedEdit(_("Login:"), align="center")
self.pass_wid = sat_widgets.Password(_("Password:"), align="center")
style = ["no_first_select"]
self.list_profile = sat_widgets.List(
[], style=style, align="center", on_change=self.on_profile_change
)
# the profiles list is retrieved asynchronously
self.refill_profiles()
# new & delete buttons
buttons = [
urwid.Button(_("New"), self.on_new_profile),
urwid.Button(_("Delete"), self.on_delete_profile),
]
buttons_flow = urwid.GridFlow(
buttons,
max([len(button.get_label()) for button in buttons]) + 4,
1,
1,
"center",
)
# second part: login information:
divider = urwid.Divider("-")
# connect button
connect_button = sat_widgets.CustomButton(
_("Connect"), self.on_connect_profiles, align="center"
)
# we now build the widget
list_walker = urwid.SimpleFocusListWalker(
[
buttons_flow,
self.list_profile,
divider,
self.login_wid,
self.pass_wid,
connect_button,
]
)
frame_body = urwid.ListBox(list_walker)
frame = urwid.Frame(
frame_body,
urwid.AttrMap(urwid.Text(_("Profile Manager"), align="center"), "title"),
)
self.main_widget = urwid.LineBox(frame)
urwid.WidgetWrap.__init__(self, self.main_widget)
self.go(autoconnect)
def keypress(self, size, key):
if key == a_key["APP_QUIT"]:
self.host.on_exit()
raise urwid.ExitMainLoop()
elif key in (a_key["FOCUS_UP"], a_key["FOCUS_DOWN"]):
focus_diff = 1 if key == a_key["FOCUS_DOWN"] else -1
list_box = self.main_widget.base_widget.body
current_focus = list_box.body.get_focus()[1]
if current_focus is None:
return
while True:
current_focus += focus_diff
if current_focus < 0 or current_focus >= len(list_box.body):
break
if list_box.body[current_focus].selectable():
list_box.set_focus(
current_focus, "above" if focus_diff == 1 else "below"
)
list_box._invalidate()
return
return super(ProfileManager, self).keypress(size, key)
def cancel_dialog(self, button):
self.host.remove_pop_up()
def new_profile(self, button, edit):
"""Create the profile"""
name = edit.get_edit_text()
self.host.bridge.profile_create(
name,
callback=lambda: self.new_profile_created(name),
errback=self.profile_creation_failure,
)
def new_profile_created(self, profile):
# new profile will be selected, and a selected profile assume the session is started
self.host.bridge.profile_start_session(
"",
profile,
callback=lambda __: self.new_profile_session_started(profile),
errback=self.profile_creation_failure,
)
def new_profile_session_started(self, profile):
self.host.remove_pop_up()
self.refill_profiles()
self.list_profile.select_value(profile)
self.current.profile = profile
self.get_connection_params(profile)
self.host.redraw()
def profile_creation_failure(self, reason):
self.host.remove_pop_up()
message = self._get_error_message(reason)
self.host.alert(_("Can't create profile"), message)
def delete_profile(self, button):
self._delete_profile()
self.host.remove_pop_up()
def on_new_profile(self, e):
pop_up_widget = sat_widgets.InputDialog(
_("New profile"),
_("Please enter a new profile name"),
cancel_cb=self.cancel_dialog,
ok_cb=self.new_profile,
)
self.host.show_pop_up(pop_up_widget)
def on_delete_profile(self, e):
if self.current.profile:
pop_up_widget = sat_widgets.ConfirmDialog(
_("Are you sure you want to delete the profile {} ?").format(
self.current.profile
),
no_cb=self.cancel_dialog,
yes_cb=self.delete_profile,
)
self.host.show_pop_up(pop_up_widget)
def on_connect_profiles(self, button):
"""Connect the profiles and start the main widget
@param button: the connect button
"""
self._on_connect_profiles()
def reset_fields(self):
"""Set profile to None, and reset fields"""
super(ProfileManager, self).reset_fields()
self.list_profile.unselect_all(invisible=True)
def set_profiles(self, profiles):
"""Update the list of profiles"""
self.list_profile.change_values(profiles)
self.host.redraw()
def get_profiles(self):
return self.list_profile.get_selected_values()
def get_jid(self):
return self.login_wid.get_edit_text()
def getPassword(self):
return self.pass_wid.get_edit_text()
def set_jid(self, jid_):
self.login_wid.set_edit_text(jid_)
self.current.login = jid_
self.host.redraw() # FIXME: redraw should be avoided
def set_password(self, password):
self.pass_wid.set_edit_text(password)
self.current.password = password
self.host.redraw()
def on_profile_change(self, list_wid, widget=None, selected=None):
"""This is called when a profile is selected in the profile list.
@param list_wid: the List widget who sent the event
"""
self.update_connection_params()
focused = list_wid.focus
selected = focused.get_state() if focused is not None else False
if not selected: # profile was just unselected
return
focused.set_state(
False, invisible=True
) # we don't want the widget to be selected until we are sure we can access it
def authenticate_cb(data, cb_id, profile):
if C.bool(data.pop("validated", C.BOOL_FALSE)):
self.current.profile = profile
focused.set_state(True, invisible=True)
self.get_connection_params(profile)
self.host.redraw()
self.host.action_manager(data, callback=authenticate_cb, profile=profile)
self.host.action_launch(
C.AUTHENTICATE_PROFILE_ID, callback=authenticate_cb, profile=focused.text
)