Mercurial > libervia-backend
view libervia/tui/base.py @ 4326:5fd6a4dc2122
cli (output/std): use `rich` to output JSON.
author | Goffi <goffi@goffi.org> |
---|---|
date | Wed, 20 Nov 2024 11:38:44 +0100 |
parents | 0d7bb4df2343 |
children |
line wrap: on
line source
#!/usr/bin/env python3 # Libervia TUI # Copyright (C) 2009-2016 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 _, D_ from libervia.tui.constants import Const as C from libervia.backend.core import log_config log_config.libervia_configure(C.LOG_BACKEND_STANDARD, C) from libervia.backend.core import log as logging log = logging.getLogger(__name__) from libervia.backend.tools import config as sat_config import urwid from urwid.util import is_wide_char from urwid_satext import sat_widgets from libervia.frontends.quick_frontend.quick_app import QuickApp from libervia.frontends.quick_frontend import quick_utils from libervia.frontends.quick_frontend import quick_chat from libervia.tui.profile_manager import ProfileManager from libervia.tui.contact_list import ContactList from libervia.tui.chat import Chat from libervia.tui import xmlui from libervia.tui.progress import Progress from libervia.tui.notify import Notify from libervia.tui.keys import action_key_map as a_key from libervia.tui import config from libervia.frontends.tools.misc import InputHistory from libervia.backend.tools.common import dynamic_import from libervia.frontends.tools import jid import signal import sys ## bridge handling # we get bridge name from conf and initialise the right class accordingly main_config = sat_config.parse_main_conf() bridge_name = sat_config.config_get(main_config, "", "bridge", "dbus") if "dbus" not in bridge_name: print("only D-Bus bridge is currently supported") sys.exit(3) class EditBar(sat_widgets.ModalEdit): """ The modal edit bar where you would enter messages and commands. """ def __init__(self, host): modes = { None: (C.MODE_NORMAL, ""), a_key["MODE_INSERTION"]: (C.MODE_INSERTION, "> "), a_key["MODE_COMMAND"]: (C.MODE_COMMAND, ":"), } # XXX: captions *MUST* be unicode super(EditBar, self).__init__(modes) self.host = host self.set_completion_method(self._text_completion) urwid.connect_signal(self, "click", self.on_text_entered) def _text_completion(self, text, completion_data, mode): if mode == C.MODE_INSERTION: if self.host.selected_widget is not None: try: completion = self.host.selected_widget.completion except AttributeError: return text else: return completion(text, completion_data) else: return text def on_text_entered(self, editBar): """Called when text is entered in the main edit bar""" if self.mode == C.MODE_INSERTION: if isinstance(self.host.selected_widget, quick_chat.QuickChat): chat_widget = self.host.selected_widget self.host.message_send( chat_widget.target, {"": editBar.get_edit_text()}, # TODO: handle language mess_type=( C.MESS_TYPE_GROUPCHAT if chat_widget.type == C.CHAT_GROUP else C.MESS_TYPE_CHAT ), # TODO: put this in QuickChat errback=lambda failure: self.host.show_dialog( _("Error while sending message ({})").format(failure), type="error", ), profile_key=chat_widget.profile, ) editBar.set_edit_text("") elif self.mode == C.MODE_COMMAND: self.command_handler() def command_handler(self): # TODO: separate class with auto documentation (with introspection) # and completion method tokens = self.get_edit_text().split(" ") command, args = tokens[0], tokens[1:] if command == "quit": self.host.on_exit() raise urwid.ExitMainLoop() elif command == "messages": wid = sat_widgets.GenericList(logging.memory_get()) self.host.select_widget(wid) # FIXME: reactivate the command # elif command == 'presence': # values = [value for value in commonConst.PRESENCE.keys()] # values = [value if value else 'online' for value in values] # the empty value actually means 'online' # if args and args[0] in values: # presence = '' if args[0] == 'online' else args[0] # self.host.status_bar.on_change(user_data=sat_widgets.ClickableText(commonConst.PRESENCE[presence])) # else: # self.host.status_bar.on_presence_click() # elif command == 'status': # if args: # self.host.status_bar.on_change(user_data=sat_widgets.AdvancedEdit(args[0])) # else: # self.host.status_bar.on_status_click() elif command == "history": widget = self.host.selected_widget if isinstance(widget, quick_chat.QuickChat): try: limit = int(args[0]) except (IndexError, ValueError): limit = 50 widget.update_history(size=limit, profile=widget.profile) elif command == "search": widget = self.host.selected_widget if isinstance(widget, quick_chat.QuickChat): pattern = " ".join(args) if not pattern: self.host.notif_bar.add_message( D_("Please specify the globbing pattern to search for") ) else: widget.update_history( size=C.HISTORY_LIMIT_NONE, filters={"search": pattern}, profile=widget.profile, ) elif command == "filter": # FIXME: filter is now only for current widget, # need to be able to set it globally or per widget widget = self.host.selected_widget # FIXME: Q&D way, need to be more generic if isinstance(widget, quick_chat.QuickChat): widget.set_filter(args) elif command in ("topic", "suject", "title"): try: new_title = args[0].strip() except IndexError: new_title = None widget = self.host.selected_widget if isinstance(widget, quick_chat.QuickChat) and widget.type == C.CHAT_GROUP: widget.on_subject_dialog(new_title) else: return self.set_edit_text("") def _history_cb(self, text): self.set_edit_text(text) self.set_edit_pos(len(text)) def keypress(self, size, key): """Callback when a key is pressed. Send "composing" states and move the index of the temporary history stack.""" if key == a_key["MODAL_ESCAPE"]: # first save the text to the current mode, then change to NORMAL self.host._update_input_history(self.get_edit_text(), mode=self.mode) self.host._update_input_history(mode=C.MODE_NORMAL) if self._mode == C.MODE_NORMAL and key in self._modes: self.host._update_input_history(mode=self._modes[key][0]) if key == a_key["HISTORY_PREV"]: self.host._update_input_history( self.get_edit_text(), -1, self._history_cb, self.mode ) return elif key == a_key["HISTORY_NEXT"]: self.host._update_input_history( self.get_edit_text(), +1, self._history_cb, self.mode ) return elif key == a_key["EDIT_ENTER"]: self.host._update_input_history(self.get_edit_text(), mode=self.mode) else: if ( self._mode == C.MODE_INSERTION and isinstance(self.host.selected_widget, quick_chat.QuickChat) and key not in sat_widgets.FOCUS_KEYS and key not in (a_key["HISTORY_PREV"], a_key["HISTORY_NEXT"]) and self.host.sync ): self.host.bridge.chat_state_composing( self.host.selected_widget.target, self.host.selected_widget.profile ) return super(EditBar, self).keypress(size, key) class LiberviaTUITopWidget(sat_widgets.FocusPile): """Top most widget used in LiberviaTUI""" _focus_inversed = True positions = ("menu", "body", "notif_bar", "edit_bar") can_hide = ("menu", "notif_bar") def __init__(self, body, menu, notif_bar, edit_bar): self._body = body self._menu = menu self._notif_bar = notif_bar self._edit_bar = edit_bar self._hidden = {"notif_bar"} self._focus_extra = False super(LiberviaTUITopWidget, self).__init__( [("pack", self._menu), self._body, ("pack", self._edit_bar)] ) for position in self.positions: setattr( self, position, property( lambda: self, self.widget_get(position=position), lambda pos, new_wid: self.widget_set(new_wid, position=pos), ), ) self.focus_position = len(self.contents) - 1 def get_visible_positions(self, keep=None): """Return positions that are not hidden in the right order @param keep: if not None, this position will be keep in the right order, even if it's hidden (can be useful to find its index) @return (list): list of visible positions """ return [ pos for pos in self.positions if (keep and pos == keep) or pos not in self._hidden ] def keypress(self, size, key): """Manage FOCUS keys that focus directly a main part (one of self.positions) To avoid key conflicts, a combinaison must be made with FOCUS_EXTRA then an other key """ if key == a_key["FOCUS_EXTRA"]: self._focus_extra = True return if self._focus_extra: self._focus_extra = False if key in ("m", "1"): focus = "menu" elif key in ("b", "2"): focus = "body" elif key in ("n", "3"): focus = "notif_bar" elif key in ("e", "4"): focus = "edit_bar" else: return super(LiberviaTUITopWidget, self).keypress(size, key) if focus in self._hidden: return self.focus_position = self.get_visible_positions().index(focus) return return super(LiberviaTUITopWidget, self).keypress(size, key) def widget_get(self, position): if not position in self.positions: raise ValueError("Unknown position {}".format(position)) return getattr(self, "_{}".format(position)) def widget_set(self, widget, position): if not position in self.positions: raise ValueError("Unknown position {}".format(position)) return setattr(self, "_{}".format(position), widget) def hide_switch(self, position): if not position in self.can_hide: raise ValueError("Can't switch position {}".format(position)) hide = not position in self._hidden widget = self.widget_get(position) idx = self.get_visible_positions(position).index(position) if hide: del self.contents[idx] self._hidden.add(position) else: self.contents.insert(idx, (widget, ("pack", None))) self._hidden.remove(position) def show(self, position): if position in self._hidden: self.hide_switch(position) def hide(self, position): if not position in self._hidden: self.hide_switch(position) class LiberviaTUIApp(QuickApp, InputHistory): MB_HANDLER = False AVATARS_HANDLER = False def __init__(self): bridge_module = dynamic_import.bridge(bridge_name, "libervia.frontends.bridge") if bridge_module is None: log.error("Can't import {} bridge".format(bridge_name)) sys.exit(3) else: log.debug("Loading {} bridge".format(bridge_name)) QuickApp.__init__( self, bridge_factory=bridge_module.bridge, xmlui=xmlui, check_options=quick_utils.check_options, connect_bridge=False, ) ## main loop setup ## event_loop = ( urwid.GLibEventLoop if "dbus" in bridge_name else urwid.TwistedEventLoop ) self.loop = urwid.MainLoop( urwid.SolidFill(), C.PALETTE, event_loop=event_loop(), input_filter=self.input_filter, unhandled_input=self.key_handler, ) @classmethod def run(cls): cls().start() def on_bridge_connected(self): ##misc setup## self._visible_widgets = set() self.notif_bar = sat_widgets.NotificationBar() urwid.connect_signal(self.notif_bar, "change", self.on_notification) self.progress_wid = self.widgets.get_or_create_widget( Progress, None, on_new_widget=None ) urwid.connect_signal( self.notif_bar.progress, "click", lambda x: self.select_widget(self.progress_wid), ) self.__saved_overlay = None self.x_notify = Notify() # we already manage exit with a_key['APP_QUIT'], so we don't want C-c signal.signal(signal.SIGINT, signal.SIG_IGN) sat_conf = sat_config.parse_main_conf() self._bracketed_paste = C.bool( sat_config.config_get(sat_conf, C.CONFIG_SECTION, "bracketed_paste", "false") ) if self._bracketed_paste: log.debug("setting bracketed paste mode as requested") sys.stdout.write("\033[?2004h") self._bracketed_mode_set = True self.loop.widget = self.main_widget = ProfileManager(self) self.post_init() @property def visible_widgets(self): return self._visible_widgets @property def mode(self): return self.editBar.mode @mode.setter def mode(self, value): self.editBar.mode = value def mode_hint(self, value): """Change mode if make sens (i.e.: if there is nothing in the editBar)""" if not self.editBar.get_edit_text(): self.mode = value def debug(self): """convenient method to reset screen and launch (i)p(u)db""" log.info("Entered debug mode") try: import pudb pudb.set_trace() except ImportError: import os os.system("reset") try: import ipdb ipdb.set_trace() except ImportError: import pdb pdb.set_trace() def redraw(self): """redraw the screen""" try: self.loop.draw_screen() except AttributeError: pass def start(self): self.connect_bridge() self.loop.run() def post_init(self): try: config.apply_config(self) except Exception as e: log.error("configuration error: {}".format(e)) popup = self.alert( _("Configuration Error"), _( "Something went wrong while reading the configuration, please check :messages" ), ) if self.options.profile: self._early_popup = popup else: self.show_pop_up(popup) super(LiberviaTUIApp, self).post_init(self.main_widget) def keys_to_text(self, keys): """Generator return normal text from urwid keys""" for k in keys: if k == "tab": yield "\t" elif k == "enter": yield "\n" elif is_wide_char(k, 0) or (len(k) == 1 and ord(k) >= 32): yield k def input_filter(self, input_, raw): if self.__saved_overlay and input_ != a_key["OVERLAY_HIDE"]: return ## paste detection/handling if ( len(input_) > 1 # XXX: it may be needed to increase this value if buffer and not isinstance( input_[0], tuple ) # or other things result in several chars at once and not "window resize" in input_ ): # (e.g. using LiberviaTUI through ssh). Need some testing # and experience to adjust value. if input_[0] == "begin paste" and not self._bracketed_paste: log.info("Bracketed paste mode detected") self._bracketed_paste = True if self._bracketed_paste: # after this block, extra will contain non pasted keys # and input_ will contain pasted keys try: begin_idx = input_.index("begin paste") except ValueError: # this is not a paste, maybe we have something buffering # or bracketed mode is set in conf but not enabled in term extra = input_ input_ = [] else: try: end_idx = input_.index("end paste") except ValueError: log.warning("missing end paste sequence, discarding paste") extra = input_[:begin_idx] del input_[begin_idx:] else: extra = input_[:begin_idx] + input_[end_idx + 1 :] input_ = input_[begin_idx + 1 : end_idx] else: extra = None log.debug("Paste detected (len {})".format(len(input_))) try: edit_bar = self.editBar except AttributeError: log.warning("Paste treated as normal text: there is no edit bar yet") if extra is None: extra = [] extra.extend(input_) else: if self.main_widget.focus == edit_bar: # XXX: if a paste is detected, we append it directly to the edit bar text # so the user can check it and press [enter] if it's OK buf_paste = "".join(self.keys_to_text(input_)) pos = edit_bar.edit_pos edit_bar.set_edit_text( "{}{}{}".format( edit_bar.edit_text[:pos], buf_paste, edit_bar.edit_text[pos:] ) ) edit_bar.edit_pos += len(buf_paste) else: # we are not on the edit_bar, # so we treat pasted text as normal text if extra is None: extra = [] extra.extend(input_) if not extra: return input_ = extra ## end of paste detection/handling for i in input_: if isinstance(i, tuple): if i[0] == "mouse press": if i[1] == 4: # Mouse wheel up input_[input_.index(i)] = a_key["HISTORY_PREV"] if i[1] == 5: # Mouse wheel down input_[input_.index(i)] = a_key["HISTORY_NEXT"] return input_ def key_handler(self, input_): if input_ == a_key["MENU_HIDE"]: """User want to (un)hide the menu roller""" try: self.main_widget.hide_switch("menu") except AttributeError: pass elif input_ == a_key["NOTIFICATION_NEXT"]: """User wants to see next notification""" self.notif_bar.show_next() elif input_ == a_key["OVERLAY_HIDE"]: """User wants to (un)hide overlay window""" if isinstance(self.loop.widget, urwid.Overlay): self.__saved_overlay = self.loop.widget self.loop.widget = self.main_widget else: if self.__saved_overlay: self.loop.widget = self.__saved_overlay self.__saved_overlay = None elif ( input_ == a_key["DEBUG"] and ".dev0" in self.bridge.version_get() ): # Debug only for dev versions self.debug() elif input_ == a_key["CONTACTS_HIDE"]: # user wants to (un)hide the contact lists try: for wid, options in self.center_part.contents: if self.contact_lists_pile is wid: self.center_part.contents.remove((wid, options)) break else: self.center_part.contents.insert( 0, (self.contact_lists_pile, ("weight", 2, False)) ) except AttributeError: # The main widget is not built (probably in Profile Manager) pass elif input_ == "window resize": width, height = self.loop.screen_size if height <= 5 and width <= 35: if not "save_main_widget" in dir(self): self.save_main_widget = self.loop.widget self.loop.widget = urwid.Filler( urwid.Text(_("Pleeeeasse, I can't even breathe !")) ) else: if "save_main_widget" in dir(self): self.loop.widget = self.save_main_widget del self.save_main_widget try: return self.menu_roller.check_shortcuts(input_) except AttributeError: return input_ def add_menus(self, menu, type_filter, menu_data=None): """Add cached menus to instance @param menu: sat_widgets.Menu instance @param type_filter: menu type like is sat.core.sat_main.import_menu @param menu_data: data to send with these menus """ def add_menu_cb(callback_id): self.action_launch(callback_id, menu_data, profile=self.current_profile) for id_, type_, path, path_i18n, extra in self.bridge.menus_get( "", C.NO_SECURITY_LIMIT ): # TODO: manage extra if type_ != type_filter: continue if len(path) != 2: raise NotImplementedError("Menu with a path != 2 are not implemented yet") menu.add_menu( path_i18n[0], path_i18n[1], lambda dummy, id_=id_: add_menu_cb(id_) ) def _build_menu_roller(self): menu = sat_widgets.Menu(self.loop) general = _("General") menu.add_menu(general, _("Connect"), self.on_connect_request) menu.add_menu(general, _("Disconnect"), self.on_disconnect_request) menu.add_menu(general, _("Parameters"), self.on_param) menu.add_menu(general, _("About"), self.on_about_request) menu.add_menu(general, _("Exit"), self.on_exit_request, a_key["APP_QUIT"]) menu.add_menu(_("Contacts")) # add empty menu to save the place in the menu order groups = _("Groups") menu.add_menu(groups) menu.add_menu( groups, _("Join room"), self.on_join_room_request, a_key["ROOM_JOIN"] ) # additionals menus # FIXME: do this in a more generic way (in quickapp) self.add_menus(menu, C.MENU_GLOBAL) menu_roller = sat_widgets.MenuRoller([(_("Main menu"), menu, C.MENU_ID_MAIN)]) return menu_roller def _build_main_widget(self): self.contact_lists_pile = urwid.Pile([]) # self.center_part = urwid.Columns([('weight',2,self.contact_lists[profile]),('weight',8,Chat('',self))]) self.center_part = urwid.Columns( [ ("weight", 2, self.contact_lists_pile), ("weight", 8, urwid.Filler(urwid.Text(""))), ] ) self.editBar = EditBar(self) self.menu_roller = self._build_menu_roller() self.main_widget = LiberviaTUITopWidget( self.center_part, self.menu_roller, self.notif_bar, self.editBar ) return self.main_widget def plugging_profiles(self): self.loop.widget = self._build_main_widget() self.redraw() try: # if a popup arrived before main widget is build, we need to show it now self.show_pop_up(self._early_popup) except AttributeError: pass else: del self._early_popup def profile_plugged(self, profile): QuickApp.profile_plugged(self, profile) contact_list = self.widgets.get_or_create_widget( ContactList, None, on_new_widget=None, on_click=self.contact_selected, on_change=lambda w: self.redraw(), profile=profile, ) self.contact_lists_pile.contents.append((contact_list, ("weight", 1))) return contact_list def is_hidden(self): """Tells if the frontend window is hidden. @return bool """ return False # FIXME: implement when necessary def alert(self, title, message): """Shortcut method to create an alert message Alert will have an "OK" button, which remove it if pressed @param title(unicode): title of the dialog @param message(unicode): body of the dialog @return (urwid_satext.Alert): the created Alert instance """ popup = sat_widgets.Alert(title, message) popup.set_callback("ok", lambda dummy: self.remove_pop_up(popup)) self.show_pop_up(popup, width=75, height=20) return popup def remove_pop_up(self, widget=None): """Remove current pop-up, and if there is other in queue, show it @param widget(None, urwid.Widget): if not None remove this popup from front or queue """ # TODO: refactor popup management in a cleaner way # buttons' callback use themselve as first argument, and we never use # a Button directly in a popup, so we consider urwid.Button as None if widget is not None and not isinstance(widget, urwid.Button): if isinstance(self.loop.widget, urwid.Overlay): current_popup = self.loop.widget.top_w if not current_popup == widget: try: self.notif_bar.remove_pop_up(widget) except ValueError: log.warning( "Trying to remove an unknown widget {}".format(widget) ) return self.loop.widget = self.main_widget next_popup = self.notif_bar.get_next_popup() if next_popup: # we still have popup to show, we display it self.show_pop_up(next_popup) else: self.redraw() def show_pop_up( self, pop_up_widget, width=None, height=None, align="center", valign="middle" ): """Show a pop-up window if possible, else put it in queue @param pop_up_widget: pop up to show @param width(int, None): width of the popup None to use default @param height(int, None): height of the popup None to use default @param align: same as for [urwid.Overlay] """ if width == None: width = 75 if isinstance(pop_up_widget, xmlui.LiberviaTUINoteDialog) else 135 if height == None: height = 20 if isinstance(pop_up_widget, xmlui.LiberviaTUINoteDialog) else 40 if not isinstance(self.loop.widget, urwid.Overlay): display_widget = urwid.Overlay( pop_up_widget, self.main_widget, align, width, valign, height ) self.loop.widget = display_widget self.redraw() else: self.notif_bar.add_pop_up(pop_up_widget) def bar_notify(self, message): """ "Notify message to user via notification bar""" self.notif_bar.add_message(message) self.redraw() def notify( self, type_, entity=None, message=None, subject=None, callback=None, cb_args=None, widget=None, profile=C.PROF_KEY_NONE, ): if widget is None or widget is not None and widget != self.selected_widget: # we ignore notification if the widget is selected but we can # still do a desktop notification is the X window has not the focus super(LiberviaTUIApp, self).notify( type_, entity, message, subject, callback, cb_args, widget, profile ) # we don't want notifications without message on desktop if message is not None and not self.x_notify.has_focus(): if message is None: message = _("{app}: a new event has just happened{entity}").format( app=C.APP_NAME, entity=" ({})".format(entity) if entity else "" ) self.x_notify.send_notification(message) def new_widget(self, widget, user_action=False): """Method called when a new widget is created if suitable, the widget will be displayed @param widget(widget.LiberviaTUIWidget): created widget @param user_action(bool): if True, the widget has been created following an explicit user action. In this case, the widget may get focus immediately """ # FIXME: when several widgets are possible (e.g. with :split) # do not replace current widget when self.selected_widget != None if user_action or self.selected_widget is None: self.select_widget(widget) def select_widget(self, widget): """Display a widget if possible, else add it in the notification bar queue @param widget: BoxWidget """ assert len(self.center_part.widget_list) <= 2 wid_idx = len(self.center_part.widget_list) - 1 self.center_part.widget_list[wid_idx] = widget try: self.menu_roller.remove_menu(C.MENU_ID_WIDGET) except KeyError: log.debug("No menu to delete") self.selected_widget = widget try: on_selected = self.selected_widget.on_selected except AttributeError: pass else: on_selected() self._visible_widgets = set( [widget] ) # XXX: we can only have one widget visible at the time for now self.contact_lists.select(None) for ( wid ) in ( self.visible_widgets ): # FIXME: check if widgets.get_widgets is not more appropriate if isinstance(wid, Chat): contact_list = self.contact_lists[wid.profile] contact_list.select(wid.target) self.redraw() def remove_window(self): """Remove window showed on the right column""" # TODO: better Window management than this hack assert len(self.center_part.widget_list) <= 2 wid_idx = len(self.center_part.widget_list) - 1 self.center_part.widget_list[wid_idx] = urwid.Filler(urwid.Text("")) self.center_part.focus_position = 0 self.redraw() def add_progress(self, pid, message, profile): """Follow a SàT progression @param pid: progression id @param message: message to show to identify the progression """ self.progress_wid.add(pid, message, profile) def set_progress(self, percentage): """Set the progression shown in notification bar""" self.notif_bar.set_progress(percentage) def contact_selected(self, contact_list, entity): self.clear_notifs(entity, profile=contact_list.profile) if entity.resource: # we have clicked on a private MUC conversation chat_widget = self.widgets.get_or_create_widget( Chat, entity, on_new_widget=None, force_hash=Chat.get_private_hash(contact_list.profile, entity), profile=contact_list.profile, ) else: chat_widget = self.widgets.get_or_create_widget( Chat, entity, on_new_widget=None, profile=contact_list.profile ) self.select_widget(chat_widget) self.menu_roller.add_menu( _("Chat menu"), chat_widget.get_menu(), C.MENU_ID_WIDGET ) def _dialog_ok_cb(self, widget, data): popup, answer_cb, answer_data = data self.remove_pop_up(popup) if answer_cb is not None: answer_cb(True, answer_data) def _dialog_cancel_cb(self, widget, data): popup, answer_cb, answer_data = data self.remove_pop_up(popup) if answer_cb is not None: answer_cb(False, answer_data) def show_dialog( self, message, title="", type="info", answer_cb=None, answer_data=None ): if type == "info": popup = sat_widgets.Alert(title, message, ok_cb=answer_cb) if answer_cb is None: popup.set_callback("ok", lambda dummy: self.remove_pop_up(popup)) elif type == "error": popup = sat_widgets.Alert(title, message, ok_cb=answer_cb) if answer_cb is None: popup.set_callback("ok", lambda dummy: self.remove_pop_up(popup)) elif type == "yes/no": popup = sat_widgets.ConfirmDialog(message) popup.set_callback("yes", self._dialog_ok_cb, (popup, answer_cb, answer_data)) popup.set_callback( "no", self._dialog_cancel_cb, (popup, answer_cb, answer_data) ) else: popup = sat_widgets.Alert(title, message, ok_cb=answer_cb) if answer_cb is None: popup.set_callback("ok", lambda dummy: self.remove_pop_up(popup)) log.error("unmanaged dialog type: {}".format(type)) self.show_pop_up(popup) def dialog_failure(self, failure): """Show a failure that has been returned by an asynchronous bridge method. @param failure (defer.Failure): Failure instance """ self.alert(failure.classname, failure.message) def on_notification(self, notif_bar): """Called when a new notification has been received""" if not isinstance(self.main_widget, LiberviaTUITopWidget): # if we are not in the main configuration, we ignore the notifications bar return if self.notif_bar.can_hide(): # No notification left, we can hide the bar self.main_widget.hide("notif_bar") else: self.main_widget.show("notif_bar") self.redraw() # FIXME: invalidate cache in a more efficient way def _action_manager_unknown_error(self): self.alert(_("Error"), _("Unmanaged action")) def room_joined_handler(self, room_jid_s, room_nicks, user_nick, subject, profile): super(LiberviaTUIApp, self).room_joined_handler( room_jid_s, room_nicks, user_nick, subject, profile ) # if self.selected_widget is None: # for contact_list in self.widgets.get_widgets(ContactList): # if profile in contact_list.profiles: # contact_list.set_focus(jid.JID(room_jid_s), True) def progress_started_handler(self, pid, metadata, profile): super(LiberviaTUIApp, self).progress_started_handler(pid, metadata, profile) self.add_progress(pid, metadata.get("name", _("unkown")), profile) def progress_finished_handler(self, pid, metadata, profile): log.info("Progress {} finished".format(pid)) super(LiberviaTUIApp, self).progress_finished_handler(pid, metadata, profile) def progress_error_handler(self, pid, err_msg, profile): log.warning("Progress {pid} error: {err_msg}".format(pid=pid, err_msg=err_msg)) super(LiberviaTUIApp, self).progress_error_handler(pid, err_msg, profile) ##DIALOGS CALLBACKS## def on_join_room(self, button, edit): self.remove_pop_up() room_jid = jid.JID(edit.get_edit_text()) self.bridge.muc_join( room_jid, self.profiles[self.current_profile].whoami.node, {}, self.current_profile, callback=lambda dummy: None, errback=self.dialog_failure, ) # MENU EVENTS# def on_connect_request(self, menu): QuickApp.connect(self, self.current_profile) def on_disconnect_request(self, menu): self.disconnect(self.current_profile) def on_param(self, menu): def success(params): ui = xmlui.create(self, xml_data=params, profile=self.current_profile) ui.show() def failure(error): self.alert(_("Error"), _("Can't get parameters (%s)") % error) self.bridge.param_ui_get( app=C.APP_NAME, profile_key=self.current_profile, callback=success, errback=failure, ) def on_exit_request(self, menu): QuickApp.on_exit(self) try: if ( self._bracketed_mode_set ): # we don't unset if bracketed paste mode was detected automatically (i.e. not in conf) log.debug("unsetting bracketed paste mode") sys.stdout.write("\033[?2004l") except AttributeError: pass raise urwid.ExitMainLoop() def on_join_room_request(self, menu): """User wants to join a MUC room""" pop_up_widget = sat_widgets.InputDialog( _("Entering a MUC room"), _("Please enter MUC's JID"), default_txt=self.bridge.muc_get_default_service(), ok_cb=self.on_join_room, ) pop_up_widget.set_callback( "cancel", lambda dummy: self.remove_pop_up(pop_up_widget) ) self.show_pop_up(pop_up_widget) def on_about_request(self, menu): self.alert(_("About"), C.APP_NAME + " v" + self.bridge.version_get()) # MISC CALLBACKS# def set_presence_status(self, show="", status=None, profile=C.PROF_KEY_NONE): contact_list_wid = self.widgets.get_widget(ContactList, profiles=profile) if contact_list_wid is not None: contact_list_wid.status_bar.set_presence_status(show, status) else: log.warning("No ContactList widget found for profile {}".format(profile)) if __name__ == "__main__": LiberviaTUIApp().start()