Mercurial > libervia-backend
view frontends/src/primitivus/profile_manager.py @ 1265:e3a9ea76de35 frontends_multi_profiles
quick_frontend, primitivus: multi-profiles refactoring part 1 (big commit, sorry :p):
This refactoring allow primitivus to manage correctly several profiles at once, with various other improvments:
- profile_manager can now plug several profiles at once, requesting password when needed. No more profile plug specific method is used anymore in backend, instead a "validated" key is used in actions
- Primitivus widget are now based on a common "PrimitivusWidget" classe which mainly manage the decoration so far
- all widgets are treated in the same way (contactList, Chat, Progress, etc), no more chat_wins specific behaviour
- widgets are created in a dedicated manager, with facilities to react on new widget creation or other events
- quick_frontend introduce a new QuickWidget class, which aims to be as generic and flexible as possible. It can manage several targets (jids or something else), and several profiles
- each widget class return a Hash according to its target. For example if given a target jid and a profile, a widget class return a hash like (target.bare, profile), the same widget will be used for all resources of the same jid
- better management of CHAT_GROUP mode for Chat widgets
- some code moved from Primitivus to QuickFrontend, the final goal is to have most non backend code in QuickFrontend, and just graphic code in subclasses
- no more (un)escapePrivate/PRIVATE_PREFIX
- contactList improved a lot: entities not in roster and special entities (private MUC conversations) are better managed
- resources can be displayed in Primitivus, and their status messages
- profiles are managed in QuickFrontend with dedicated managers
This is work in progress, other frontends are broken. Urwid SàText need to be updated. Most of features of Primitivus should work as before (or in a better way ;))
author | Goffi <goffi@goffi.org> |
---|---|
date | Wed, 10 Dec 2014 19:00:09 +0100 |
parents | 49d39b619e5d |
children | 7cf32aeeebdb |
line wrap: on
line source
#!/usr/bin/python # -*- coding: utf-8 -*- # Primitivus: a SAT frontend # Copyright (C) 2009, 2010, 2011, 2012, 2013, 2014 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 sat.core.i18n import _ from sat.core import log as logging log = logging.getLogger(__name__) from sat_frontends.primitivus.constants import Const as C from sat_frontends.primitivus.keys import action_key_map as a_key from urwid_satext import sat_widgets import urwid class ProfileRecord(object): def __init__(self, profile=None, login=None, password=None): self._profile = profile self._login = login self._password = password @property def profile(self): return self._profile @profile.setter def profile(self, value): self._profile = value # if we change the profile, # we must have no login/password until backend give them self._login = self._password = None @property def login(self): return self._login @login.setter def login(self, value): self._login = value @property def password(self): return self._password @password.setter def password(self, value): self._password = value class ProfileManager(urwid.WidgetWrap): """Class with manage profiles creation/deletion/connection""" def __init__(self, host, autoconnect=None): """Create the manager @param host: %(doc_host)s @param autoconnect(iterable): list of profiles to connect automatically """ self.host = host self._autoconnect = bool(autoconnect) self.current = ProfileRecord() profiles = self.host.bridge.getProfilesList() profiles.sort() #login & password box must be created before list because of onProfileChange 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(profiles, style=style, align='center', on_change=self.onProfileChange) #new & delete buttons buttons = [urwid.Button(_("New"), self.onNewProfile), urwid.Button(_("Delete"), self.onDeleteProfile)] 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.onConnectProfiles, 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) if self._autoconnect: self.autoconnect(autoconnect) def autoconnect(self, profile_keys): """Automatically connect profiles @param profile_keys(iterable): list of profile keys to connect """ if not profile_keys: log.warning("No profile given to autoconnect") return self._autoconnect = True self._autoconnect_profiles=[] self._do_autoconnect(profile_keys) def keypress(self, size, key): if key == a_key['APP_QUIT']: self.host.onExit() 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 _do_autoconnect(self, profile_keys): """Connect automatically given profiles @param profile_kes(iterable): profiles to connect """ assert self._autoconnect def authenticate_cb(callback_id, data, profile): if C.bool(data['validated']): self._autoconnect_profiles.append(profile) if len(self._autoconnect_profiles) == len(profile_keys): # all the profiles have been validated self.host.plug_profiles(self._autoconnect_profiles) else: # a profile is not validated, we go to manual mode self._autoconnect=False for profile_key in profile_keys: profile = self.host.bridge.getProfileName(profile_key) if not profile: self._autoconnect = False # manual mode msg = _("Trying to plug an unknown profile key ({})".format(profile_key)) log.warning(msg) popup = sat_widgets.Alert(_("Profile plugging in error"), msg, ok_cb=self.host.removePopUp) self.host.showPopUp(popup) break self.host.launchAction(C.AUTHENTICATE_PROFILE_ID, callback=authenticate_cb, profile=profile) def refillProfiles(self): """Update the list of profiles""" profiles = self.host.bridge.getProfilesList() profiles.sort() self.list_profile.changeValues(profiles) self.host.redraw() def cancelDialog(self, button): self.host.removePopUp() def newProfile(self, button, edit): """Create the profile""" name = edit.get_edit_text() self.host.bridge.asyncCreateProfile(name, callback=lambda: self.newProfileCreated(name), errback=self.profileCreationFailure) def newProfileCreated(self, profile): self.host.removePopUp() self.refillProfiles() self.list_profile.selectValue(profile) self.current.profile=profile self.getConnectionParams(profile) self.host.redraw() def profileCreationFailure(self, reason): self.host.removePopUp() if reason == "ConflictError": message = _("A profile with this name already exists") elif reason == "CancelError": message = _("Profile creation cancelled by backend") elif reason == "ValueError": message = _("You profile name is not valid") # TODO: print a more informative message (empty name, name starting with '@') else: message = _("Can't create profile ({})").format(reason) popup = sat_widgets.Alert(_("Can't create profile"), message, ok_cb=self.host.removePopUp) self.host.showPopUp(popup) def deleteProfile(self, button): if self.current.profile: self.host.bridge.asyncDeleteProfile(self.current.profile, callback=self.refillProfiles) self.resetFields() self.host.removePopUp() def onNewProfile(self, e): pop_up_widget = sat_widgets.InputDialog(_("New profile"), _("Please enter a new profile name"), cancel_cb=self.cancelDialog, ok_cb=self.newProfile) self.host.showPopUp(pop_up_widget) def onDeleteProfile(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.cancelDialog, yes_cb=self.deleteProfile) self.host.showPopUp(pop_up_widget) def resetFields(self): """Set profile to None, and reset fields""" self.current.profile=None self.login_wid.set_edit_text("") self.pass_wid.set_edit_text("") self.list_profile.unselectAll(invisible=True) def getConnectionParams(self, profile): """Get login and password and display them @param profile: %(doc_profile)s """ def setJID(jabberID): self.login_wid.set_edit_text(jabberID) self.current.login = jabberID self.host.redraw() # FIXME: redraw should be avoided def setPassword(password): self.pass_wid.set_edit_text(password) self.current.password = password self.host.redraw() self.host.bridge.asyncGetParamA("JabberID", "Connection", profile_key=profile, callback=setJID, errback=self.getParamError) self.host.bridge.asyncGetParamA("Password", "Connection", profile_key=profile, callback=setPassword, errback=self.getParamError) def updateConnectionParams(self): """Check if connection parameters have changed, and update them if so""" if self.current.profile: login = self.login_wid.get_edit_text() password = self.pass_wid.get_edit_text() if login != self.current.login and self.current.login is not None: self.current.login = login self.host.bridge.setParam("JabberID", login, "Connection", profile_key=self.current.profile) log.info("login updated for profile [{}]".format(self.current.profile)) if password != self.current.password and self.current.password is not None: self.current.password = password self.host.bridge.setParam("Password", password, "Connection", profile_key=self.current.profile) log.info("password updated for profile [{}]".format(self.current.profile)) def onProfileChange(self, list_wid): """This is called when a profile is selected in the profile list. @param list_wid: the List widget who sent the event """ self.updateConnectionParams() focused = list_wid.focus selected = focused.getState() if not selected: # profile was just unselected return focused.setState(False, invisible=True) # we don't want the widget to be selected until we are sure we can access it def authenticate_cb(callback_id, data, profile): if C.bool(data['validated']): self.current.profile = profile focused.setState(True, invisible=True) self.getConnectionParams(profile) self.host.redraw() self.host.launchAction(C.AUTHENTICATE_PROFILE_ID, callback=authenticate_cb, profile=focused.text) def onConnectProfiles(self, button): """Connect the profiles and start the main widget @param button: the connect button """ if self._autoconnect: pop_up_widget = sat_widgets.Alert(_('Internal error'), _('You can connect manually and automatically at the same time'), ok_cb=self.cancelDialog) self.host.showPopUp(pop_up_widget) return self.updateConnectionParams() profiles = self.list_profile.getSelectedValues() if not profiles: pop_up_widget = sat_widgets.Alert(_('No profile selected'), _('You need to create and select at least one profile before connecting'), ok_cb=self.cancelDialog) self.host.showPopUp(pop_up_widget) else: # All profiles in the list are already validated, so we can plug them directly self.host.plug_profiles(profiles) def getParamError(self, dummy): popup = sat_widgets.Alert("Error", _("Can't get profile parameter"), ok_cb=self.host.removePopUp) self.host.showPopUp(popup)