diff options
-rw-r--r-- | data/default_config.cfg | 22 | ||||
-rw-r--r-- | doc/en/configure.txt | 27 | ||||
-rw-r--r-- | doc/en/usage.txt | 12 | ||||
-rw-r--r-- | src/bookmark.py | 2 | ||||
-rw-r--r-- | src/config.py | 6 | ||||
-rw-r--r-- | src/connection.py | 4 | ||||
-rw-r--r-- | src/contact.py | 5 | ||||
-rw-r--r-- | src/core.py | 79 | ||||
-rw-r--r-- | src/plugin_manager.py | 4 | ||||
-rw-r--r-- | src/tabs.py | 288 | ||||
-rw-r--r-- | src/windows.py | 30 |
11 files changed, 377 insertions, 102 deletions
diff --git a/data/default_config.cfg b/data/default_config.cfg index dc5cc4d6..2c5f0d81 100644 --- a/data/default_config.cfg +++ b/data/default_config.cfg @@ -199,6 +199,28 @@ plugins_autoload = # with no activity, set to true. Else, set to false show_inactive_tabs = true +# If you want to show the tab names in the bottom tab bar, set this to true +show_tab_names = false + +# If you want to disable the numbers in the bottom tab bar, set this to false +# If show_tab_names and show_tab_numbers are both false, the numbers will be +# shown +show_tab_numbers = true + +# Use the contact name, the nick in the MUC instead of the full JID to +# display the tab if set to true. +use_tab_nicks = true + +# If set to false, poezio will only display the user part of the JID (before +# the @) when displaying the MUC tab name. +show_muc_jid = true + +# If this option is set to false, the roster will not show the contact JIDs +# when that is possible. +# e.g. instead of: toto (toto@example.org) (2) +# poezio will only show: toto (2) +show_roster_jids = true + # The terminal can beep on various event. Put the event you want in a list # (separated by spaces). # The events can be diff --git a/doc/en/configure.txt b/doc/en/configure.txt index b6fe41bf..36a49206 100644 --- a/doc/en/configure.txt +++ b/doc/en/configure.txt @@ -247,6 +247,33 @@ section of this documentation. If you want to show all the tabs in the Tab bar, even those with no activity, set to true. Else, set to false +*show_tab_names*:: false + + If you want to show the tab name in the bottom Tab bar, set this to true. + +*show_tab_numbers*:: true + + If you want to disable the numbers in the bottom Tab bar, set this to false. + Note that if both show_tab_names and show_tab_numbers are set to false, the + numbers will still be displayed. + +*use_tab_nicks*:: true + + The tabs have a name, and a nick, which is, for a contact, its name in the + roster, or for a private conversation, the nickname in the MUC. Set this to + true if you want to have them shown instead of the jid of the contact. + +*show_muc_jid*:: true + + Set this to false if you want to display only the *user* part of the MUC + jid. E.g. if you have poezio@muc.poezio.eu, it will be displayed as + `poezio`. This will be used only if use_tab_nicks is set to true. + +*show_roster_jids*:: true + + Set this to false if you want to hide the JIDs in the roster (and keep only + the contact names). If there is no contact name, the JID will still be + displayed. *beep_on*:: highlight private diff --git a/doc/en/usage.txt b/doc/en/usage.txt index 46e88a7e..d843a173 100644 --- a/doc/en/usage.txt +++ b/doc/en/usage.txt @@ -151,6 +151,14 @@ The commands described in this page are shown like this: You can get the same help as below with the _/help_ command. +NOTE: Use command parameters like this: + +* Do not use quotes if they are unnecessary (words without special chars) +* If the command takes several agrguments, you need to put quotes around + arguments containing special chars such as backslashes or quotes +* If the command always takes only one argument, then do not use quotes even + for words containing special chars + Global commands ~~~~~~~~~~~~~~~ @@ -364,6 +372,10 @@ Roster tab commands */groupadd <jid> <group>*:: Add the given JID to the given group (if the group does not exist, it will be created). +*/groupmove <jid> <old_group> <new_group>*:: Move the given JID from one group + to another (the JID has to be in the first group, and the new group may not + exist). + */groupremove <jid> <group>*:: Remove the given JID from the given group (if the group is empty after that, it will get deleted). diff --git a/src/bookmark.py b/src/bookmark.py index 38979697..95f42dd8 100644 --- a/src/bookmark.py +++ b/src/bookmark.py @@ -124,7 +124,7 @@ def save_privatexml(xmpp): """"Save the remote bookmarks with privatexml.""" xmpp.plugin['xep_0048'].set_bookmarks_old(stanza_storage('privatexml')) -def save_remote(xmpp, method="privatexml"): +def save_remote(xmpp, method=preferred): """Save the remote bookmarks.""" method = "privatexml" if method != 'pep' else 'pep' diff --git a/src/config.py b/src/config.py index b4b07491..e99572d7 100644 --- a/src/config.py +++ b/src/config.py @@ -50,6 +50,12 @@ class Config(RawConfigParser): return default return res + def getl(self, option, default, section=DEFSECTION): + """ + get a value and return it lowercase + """ + return self.get(option, default, section).lower() + def get_by_tabname(self, option, default, tabname, fallback=True): """ Try to get the value for the option. First we look in diff --git a/src/connection.py b/src/connection.py index e4e2cfb2..2c51a78c 100644 --- a/src/connection.py +++ b/src/connection.py @@ -62,7 +62,9 @@ class Connection(sleekxmpp.ClientXMPP): 'version':'0.7.5-dev'} if config.get('send_os_info', 'true') == 'true': info['os'] = common.get_os_info() - self.register_plugin('xep_0092', pconfig=info) + else: + info = {'name': '', 'version': ''} + self.register_plugin('xep_0092', pconfig=info) if config.get('send_time', 'true') == 'true': self.register_plugin('xep_0202') self.register_plugin('xep_0224') diff --git a/src/contact.py b/src/contact.py index 69dfb984..48f5d751 100644 --- a/src/contact.py +++ b/src/contact.py @@ -62,6 +62,11 @@ class Contact(object): return self.__item['groups'] or ['none'] @property + def resources(self): + """Resources of the contact""" + return self._resources + + @property def bare_jid(self): """The bare_jid or the contact""" return self.__item.jid diff --git a/src/core.py b/src/core.py index da9ff4da..f7447863 100644 --- a/src/core.py +++ b/src/core.py @@ -1359,14 +1359,14 @@ class Core(object): /help <command_name> """ args = arg.split() - if len(args) == 0: + if not args: msg = _('Available commands are: ') for command in self.commands: msg += "%s " % command for command in self.current_tab().commands: msg += "%s " % command msg += _("\nType /help <command_name> to know what each command does") - if len(args) >= 1: + if args: if args[0] in self.commands: msg = self.commands[args[0]][1] elif args[0] in self.current_tab().commands: @@ -1377,7 +1377,7 @@ class Core(object): def completion_help(self, the_input): commands = list(self.commands.keys()) + list(self.current_tab().commands.keys()) - return the_input.auto_completion(commands, ' ') + return the_input.auto_completion(commands, ' ', quotify=False) def command_status(self, arg): """ @@ -1521,14 +1521,14 @@ class Core(object): """ /message <jid> [message] """ - args = arg.split() + args = common.shell_split(arg) if len(args) < 1: self.command_help('message') return jid = args[0] tab = self.open_conversation_window(jid, focus=True) if len(args) > 1: - tab.command_say(arg.strip()[len(jid)+1:]) + tab.command_say(args[1]) def command_version(self, arg): """ @@ -1560,16 +1560,15 @@ class Core(object): /list <server> Opens a MucListTab containing the list of the room in the specified server """ - args = arg.split() - if len(args) > 1: - self.command_help('list') - return - elif len(args) == 0: + arg = arg.split() + if len(arg) > 1: + return self.command_help('list') + elif arg: + server = JID(arg[0]).server + else: if not isinstance(self.current_tab(), tabs.MucTab): return self.information('Please provide a server', 'Error') server = JID(self.current_tab().get_name()).server - else: - server = arg.strip() list_tab = tabs.MucListTab(server) self.add_tab(list_tab, True) self.xmpp.plugin['xep_0030'].get_items(jid=server, block=False, callback=list_tab.on_muc_list_item_received) @@ -1577,7 +1576,7 @@ class Core(object): def command_theme(self, arg): """/theme <theme name>""" args = arg.split() - if len(args) == 1: + if args: self.command_set('theme %s' % (args[0],)) warning = theming.reload_theme() if warning: @@ -1600,20 +1599,20 @@ class Core(object): theme_files = [name[:-3] for name in names if name.endswith('.py')] if not 'default' in theme_files: theme_files.append('default') - return the_input.auto_completion(theme_files, '') + return the_input.auto_completion(theme_files, '', quotify=False) def command_win(self, arg): """ /win <number> """ - args = arg.split() - if len(args) != 1: + arg = arg.strip() + if not arg: self.command_help('win') return try: - nb = int(args[0]) + nb = int(arg.split()[0]) except ValueError: - nb = arg.strip() + nb = arg if self.current_tab().nb == nb: return self.previous_tab_nb = self.current_tab().nb @@ -1636,7 +1635,7 @@ class Core(object): def completion_win(self, the_input): l = [JID(tab.get_name()).user for tab in self.tabs] l.remove('') - return the_input.auto_completion(l, ' ') + return the_input.auto_completion(l, ' ', quotify=False) def completion_join(self, the_input): """ @@ -1703,6 +1702,7 @@ class Core(object): jids_list = ['%s/%s' % (jid.bare, nick) for nick in nicks] return the_input.auto_completion(jids_list, '') muc_list = [tab.get_name() for tab in self.tabs if isinstance(tab, tabs.MucTab)] + muc_list.append('*') return the_input.auto_completion(muc_list, '') def completion_bookmark(self, the_input): @@ -1737,6 +1737,7 @@ class Core(object): jids_list = ['%s/%s' % (jid.bare, nick) for nick in nicks] return the_input.auto_completion(jids_list, '') muc_list = [tab.get_name() for tab in self.tabs if isinstance(tab, tabs.MucTab)] + muc_list.append('*') return the_input.auto_completion(muc_list, '') def completion_version(self, the_input): @@ -1744,7 +1745,7 @@ class Core(object): n = len(the_input.get_text().split()) if n > 2 or (n == 2 and the_input.get_text().endswith(' ')): return - return the_input.auto_completion([jid for jid in roster.jids()], '') + return the_input.auto_completion([jid for jid in roster.jids()], '', quotify=False) def completion_list(self, the_input): muc_serv_list = [] @@ -1753,7 +1754,7 @@ class Core(object): tab.get_name() not in muc_serv_list: muc_serv_list.append(JID(tab.get_name()).server) if muc_serv_list: - return the_input.auto_completion(muc_serv_list, ' ') + return the_input.auto_completion(muc_serv_list, ' ', quotify=False) def command_join(self, arg, histo_length=None): """ @@ -1853,15 +1854,28 @@ class Core(object): roomname = tab.get_name() if tab.joined: nick = tab.own_nick + elif args[0] == '*': + for tab in self.tabs: + if isinstance(tab, tabs.MucTab): + b = bookmark.get_by_jid(tab.get_name()) + if not b: + b = bookmark.Bookmark(tab.get_name(), autojoin=True, method="local") + bookmark.bookmarks.append(b) + else: + b.method = "local" + bookmark.save_local() + self.information('Bookmarks added and saved.', 'Info') + return else: info = JID(args[0]) if info.resource != '': nick = info.resource roomname = info.bare - if roomname == '': + if not roomname: if not isinstance(self.current_tab(), tabs.MucTab): return roomname = self.current_tab().get_name() + bm = bookmark.get_by_jid(roomname) if not bm: bm = bookmark.Bookmark(jid=roomname) @@ -1881,6 +1895,7 @@ class Core(object): """ /bookmark [room][/nick] [autojoin] [password] """ + if config.get('use_remote_bookmarks', 'true').lower() == 'false': self.command_bookmark_local(arg) return @@ -1895,6 +1910,24 @@ class Core(object): nick = tab.own_nick autojoin = True password = None + elif args[0] == '*': + if len(args) > 1: + autojoin = False if args[1].lower() == 'false' else True + else: + autojoin = True + for tab in self.tabs: + if isinstance(tab, tabs.MucTab): + b = bookmark.get_by_jid(tab.get_name()) + if not b: + b = bookmark.Bookmark(tab.get_name(), autojoin=autojoin) + bookmark.bookmarks.append(b) + else: + b.method = "local" + if bookmark.save_remote(self.xmpp, self): + self.information("Bookmarks added.", "Info") + else: + self.information("Could not add the bookmarks.", "Info") + return else: info = JID(args[0]) if info.resource != '': @@ -1923,7 +1956,7 @@ class Core(object): bm.password = password if autojoin: bm.autojoin = autojoin - if bookmark.save_remote(self.xmpp, self): + if bookmark.save_remote(self.xmpp): self.information('Bookmark added.', 'Info') self.information(_('Your remote bookmarks are now: %s') % [b for b in bookmark.bookmarks if b.method in ('pep', 'privatexml')], 'Info') diff --git a/src/plugin_manager.py b/src/plugin_manager.py index 98879841..40367052 100644 --- a/src/plugin_manager.py +++ b/src/plugin_manager.py @@ -213,10 +213,10 @@ class PluginManager(object): self.core.information(_('Completion failed: %s' % e), 'Error') return plugins_files = [name[:-3] for name in names if name.endswith('.py')] - return the_input.auto_completion(plugins_files, '') + return the_input.auto_completion(plugins_files, '', quotify=False) def completion_unload(self, the_input): """ completion function that completes the name of the plugins that are loaded """ - return the_input.auto_completion(list(self.plugins.keys()), '') + return the_input.auto_completion(list(self.plugins.keys()), '', quotify=False) diff --git a/src/tabs.py b/src/tabs.py index fceacfe7..df5a9b90 100644 --- a/src/tabs.py +++ b/src/tabs.py @@ -44,7 +44,7 @@ from sleekxmpp.xmlstream import matcher from sleekxmpp.xmlstream.handler import Callback from config import config from roster import RosterGroup, roster -from contact import Contact +from contact import Contact, Resource from text_buffer import TextBuffer from user import User from os import getenv, path @@ -211,7 +211,7 @@ class Tab(object): # complete the command's name words = ['/%s'% (name) for name in self.core.commands] +\ ['/%s' % (name) for name in self.commands] - the_input.auto_completion(words, '') + the_input.auto_completion(words, '', quotify=False) # Do not try to cycle command completion if there was only # one possibily. The next tab will complete the argument. # Otherwise we would need to add a useless space before being @@ -269,6 +269,12 @@ class Tab(object): """ return self.__class__.__name__ + def get_nick(self): + """ + Get the nick of the tab (defaults to its name) + """ + return self.get_name() + def get_text_window(self): """ Returns the principal TextWin window, if there's one @@ -415,7 +421,7 @@ class ChatTab(Tab): for word in txt.split(): if len(word) >= 4 and word not in words: words.append(word) - self.input.auto_completion(words, ' ') + self.input.auto_completion(words, ' ', quotify=False) def on_enter(self): txt = self.input.key_enter() @@ -629,23 +635,23 @@ class MucTab(ChatTab): if user.nick != self.own_nick] contact_list = [jid for jid in roster.jids()] userlist.extend(contact_list) - return the_input.auto_completion(userlist, '') + return the_input.auto_completion(userlist, '', quotify=False) def completion_nick(self, the_input): """Completion for /nick""" nicks = [os.environ.get('USER'), config.get('default_nick', ''), self.core.get_bookmark_nickname(self.get_name())] while nicks.count(''): nicks.remove('') - return the_input.auto_completion(nicks, '') + return the_input.auto_completion(nicks, '', quotify=False) def completion_recolor(self, the_input): - return the_input.auto_completion(['random'], '') + return the_input.auto_completion(['random'], '', quotify=False) def completion_ignore(self, the_input): """Completion for /ignore""" userlist = [user.nick for user in self.users] userlist.remove(self.own_nick) - return the_input.auto_completion(userlist, '') + return the_input.auto_completion(userlist, '', quotify=False) def completion_role(self, the_input): """Completion for /role""" @@ -693,18 +699,20 @@ class MucTab(ChatTab): self.input.refresh() def command_info(self, arg): - args = common.shell_split(arg) - if len(args) != 1: - return self.core.information("Info command takes only 1 argument") - user = self.get_user_by_name(args[0]) + """ + /info <nick> + """ + if not arg: + return self.core.command_help('info') + user = self.get_user_by_name(arg) if not user: - return self.core.information("Unknown user: %s" % args[0]) - info = '%s%s: show: %s, affiliation: %s, role: %s%s' % (args[0], - ' (%s)' % user.jid if user.jid else '', - user.show or 'Available', - user.role or 'None', - user.affiliation or 'None', - '\n%s' % user.status if user.status else '') + return self.core.information("Unknown user: %s" % arg) + info = '%s%s: show: %s, affiliation: %s, role: %s%s' % (arg, + ' (%s)' % user.jid if user.jid else '', + user.show or 'Available', + user.role or 'None', + user.affiliation or 'None', + '\n%s' % user.status if user.status else '') self.core.information(info, 'Info') def command_configure(self, arg): @@ -738,9 +746,10 @@ class MucTab(ChatTab): def command_recolor(self, arg): """ + /recolor [random] Re-assign color to the participants of the room """ - args = common.shell_split(arg) + arg = arg.strip() compare_users = lambda x: x.last_talked users = list(self.users) sorted_users = sorted(users, key=compare_users, reverse=True) @@ -750,9 +759,8 @@ class MucTab(ChatTab): sorted_users.remove(user) user.color = get_theme().COLOR_OWN_NICK colors = list(get_theme().LIST_COLOR_NICKNAMES) - if len(args) >= 1: - if args[0] == 'random': - random.shuffle(colors) + if arg and arg == 'random': + random.shuffle(colors) for i, user in enumerate(sorted_users): user.color = colors[i % len(colors)] self.text_win.rebuild_everything(self._text_buffer) @@ -768,30 +776,29 @@ class MucTab(ChatTab): if not res: return self.core.information('Could not get the software version from %s' % (jid,), 'Warning') version = '%s is running %s version %s on %s' % (jid, - res.get('name') or _('an unknown software'), - res.get('version') or _('unknown'), - res.get('os') or _('on an unknown platform')) + res.get('name') or _('an unknown software'), + res.get('version') or _('unknown'), + res.get('os') or _('on an unknown platform')) self.core.information(version, 'Info') - args = common.shell_split(arg) - if len(args) < 1: - return - if args[0] in [user.nick for user in self.users]: - jid = self.name + '/' + args[0] + if not arg: + return self.core.command_help('version') + if arg in [user.nick for user in self.users]: + jid = JID(self.name) + jid.resource = arg else: - jid = args[0] + jid = JID(arg) self.core.xmpp.plugin['xep_0092'].get_version(jid, callback=callback) def command_nick(self, arg): """ /nick <nickname> """ - args = common.shell_split(arg) - if len(args) != 1: - return - nick = args[0] + if not arg: + return self.core.command_help('nick') + nick = arg if not self.joined: - return + return self.core.information('/nick only works in joined rooms', 'Info') current_status = self.core.get_status() muc.change_nick(self.core.xmpp, self.name, nick, current_status.message, current_status.show) @@ -799,11 +806,7 @@ class MucTab(ChatTab): """ /part [msg] """ - args = arg.split() - if len(args): - arg = ' '.join(args) - else: - arg = None + arg = arg.strip() if self.joined: self.disconnect() muc.leave_groupchat(self.core.xmpp, self.name, self.own_nick, arg) @@ -823,7 +826,6 @@ class MucTab(ChatTab): self.command_part(arg) self.core.close_tab() - def command_query(self, arg): """ /query <nick> [message] @@ -911,7 +913,7 @@ class MucTab(ChatTab): /kick <nick> [reason] """ args = common.shell_split(arg) - if not len(args): + if not args: self.core.command_help('kick') else: if len(args) > 1: @@ -988,11 +990,10 @@ class MucTab(ChatTab): """ /ignore <nick> """ - args = common.shell_split(arg) - if len(args) != 1: + if not arg: self.core.command_help('ignore') return - nick = args[0] + nick = arg user = self.get_user_by_name(nick) if not user: self.core.information(_('%s is not in the room') % nick) @@ -1006,11 +1007,10 @@ class MucTab(ChatTab): """ /unignore <nick> """ - args = common.shell_split(arg) - if len(args) != 1: + if not arg: self.core.command_help('unignore') return - nick = args[0] + nick = arg user = self.get_user_by_name(nick) if not user: self.core.information(_('%s is not in the room') % nick) @@ -1021,7 +1021,7 @@ class MucTab(ChatTab): self.core.information(_('%s is now unignored') % nick) def completion_unignore(self, the_input): - return the_input.auto_completion([user.nick for user in self.ignores], ' ') + return the_input.auto_completion([user.nick for user in self.ignores], ' ', quotify=False) def resize(self): """ @@ -1092,6 +1092,11 @@ class MucTab(ChatTab): def get_name(self): return self.name + def get_nick(self): + if config.getl('show_muc_jid', 'true') == 'false': + return JID(self.name).user + return self.name + def get_text_window(self): return self.text_win @@ -1591,6 +1596,9 @@ class PrivateTab(ChatTab): def get_name(self): return self.name + def get_nick(self): + return JID(self.name).resource + def on_input(self, key, raw): if not raw and key in self.key_func: self.key_func[key]() @@ -1702,6 +1710,9 @@ class RosterInfoTab(Tab): self.key_func["M-[1;5B"] = self.move_cursor_to_next_group self.key_func["M-[1;5A"] = self.move_cursor_to_prev_group self.key_func["o"] = self.toggle_offline_show + self.key_func["v"] = self.get_contact_version + self.key_func["i"] = self.show_contact_info + self.key_func["n"] = self.change_contact_name self.key_func["s"] = self.start_search self.key_func["S"] = self.start_search_slow self.commands['deny'] = (self.command_deny, _("Usage: /deny [jid]\nDeny: Deny your presence to the provided JID (or the selected contact in your roster), who is asking you to be in his/here roster."), self.completion_deny) @@ -1709,6 +1720,7 @@ class RosterInfoTab(Tab): self.commands['add'] = (self.command_add, _("Usage: /add <jid>\nAdd: Add the specified JID to your roster, ask him to allow you to see his presence, and allow him to see your presence."), None) self.commands['name'] = (self.command_name, _("Usage: /name <jid> <name>\nSet the given JID's name."), self.completion_name) self.commands['groupadd'] = (self.command_groupadd, _("Usage: /groupadd <jid> <group>\nAdd the given JID to the given group."), self.completion_groupadd) + self.commands['groupmove'] = (self.command_groupmove, _("Usage: /groupchange <jid> <old group> <new group>\nMoves the given JID from the old group to the new group."), self.completion_groupmove) self.commands['groupremove'] = (self.command_groupremove, _("Usage: /groupremove <jid> <group>\nRemove the given JID from the given group."), self.completion_groupremove) self.commands['remove'] = (self.command_remove, _("Usage: /remove [jid]\nRemove: Remove the specified JID from your roster. This wil unsubscribe you from its presence, cancel its subscription to yours, and remove the item from your roster."), self.completion_remove) self.commands['export'] = (self.command_export, _("Usage: /export [/path/to/file]\nExport: Export your contacts into /path/to/file if specified, or $HOME/poezio_contacts if not."), self.completion_file) @@ -1769,12 +1781,12 @@ class RosterInfoTab(Tab): self.core.information_win.rebuild_everything(self.core.information_buffer) self.refresh() - def command_deny(self, args): + def command_deny(self, arg): """ + /deny [jid] Denies a JID from our roster """ - args = args.split() - if not args: + if not arg: item = self.roster_win.selected_row if isinstance(item, Contact): jid = item.bare_jid @@ -1782,7 +1794,10 @@ class RosterInfoTab(Tab): self.core.information('No subscription to deny') return else: - jid = JID(args[0]).bare + jid = JID(arg).bare + if not jid in [jid for jid in roster.jids()]: + self.core.information('No subscription to deny') + return contact = roster[jid] if contact: @@ -1801,13 +1816,13 @@ class RosterInfoTab(Tab): return self.core.information('Already subscribed.', 'Roster') roster.add(jid) - def command_name(self, args): + def command_name(self, arg): """ Set a name for the specified JID in your roster """ - args = args.split(None, 1) - if len(args) < 1: - return + args = common.shell_split(arg) + if not args: + return self.core.command_help('name') jid = JID(args[0]).bare name = args[1] if len(args) == 2 else '' @@ -1852,11 +1867,57 @@ class RosterInfoTab(Tab): if self.core.xmpp.update_roster(jid, name=name, groups=new_groups, subscription=subscription): roster.update_contact_groups(jid) + def command_groupmove(self, arg): + """ + Remove the specified JID from the first specified group and add it to the second one + """ + args = common.shell_split(arg) + if len(args) != 3: + return self.core.command_help('groupmove') + jid = JID(args[0]).bare + group_from = args[1] + group_to = args[2] + + contact = roster[jid.bare] + if not contact: + self.core.information(_('No such JID in roster'), 'Error') + return + + new_groups = set(contact.groups) + if 'none' in new_groups: + new_groups.remove('none') + + if group_to == 'none' or group_from == 'none': + self.core.information(_('"none" is not a group.'), 'Error') + return + + if group_from not in new_groups: + self.core.information(_('JID not in first group'), 'Error') + return + + if group_to in new_groups: + self.core.information(_('JID already in second group'), 'Error') + return + + if group_to == group_from: + self.core.information(_('The groups are the same.'), 'Error') + return + + new_groups.add(group_to) + if 'none' in new_groups: + new_groups.remove('none') + + new_groups.remove(group_from) + name = contact.name + subscription = contact.subscription + if self.core.xmpp.update_roster(jid, name=name, groups=new_groups, subscription=subscription): + roster.edit_groups_of_contact(contact, new_groups) + def command_groupremove(self, args): """ - Remove the specified JID to the specified group + Remove the specified JID from the specified group """ - args = args.split(None, 1) + args = common.shell_split(args) if len(args) != 2: return jid = JID(args[0]).bare @@ -1956,7 +2017,7 @@ class RosterInfoTab(Tab): def completion_name(self, the_input): text = the_input.get_text() - n = len(text.split()) + n = len(common.shell_split(text)) if text.endswith(' '): n += 1 @@ -1967,7 +2028,7 @@ class RosterInfoTab(Tab): def completion_groupadd(self, the_input): text = the_input.get_text() - n = len(text.split()) + n = len(common.shell_split(text)) if text.endswith(' '): n += 1 @@ -1979,9 +2040,32 @@ class RosterInfoTab(Tab): return the_input.auto_completion(groups, '') return False + def completion_groupmove(self, the_input): + text = the_input.get_text() + args = common.shell_split(text) + n = len(args) + if text.endswith(' '): + n += 1 + + if n == 2: + jids = [jid for jid in roster.jids()] + return the_input.auto_completion(jids, '') + elif n == 3: + contact = roster[args[1]] + if not contact: + return False + groups = list(contact.groups) + if 'none' in groups: + groups.remove('none') + return the_input.auto_completion(groups, '') + elif n == 4: + groups = [group for group in roster.groups] + return the_input.auto_completion(groups, '') + return False + def completion_groupremove(self, the_input): text = the_input.get_text() - args = text.split() + args = common.shell_split(text) n = len(args) if text.endswith(' '): n += 1 @@ -2008,14 +2092,13 @@ class RosterInfoTab(Tab): """ jids = [str(contact.bare_jid) for contact in roster.contacts.values()\ if contact.pending_in] - return the_input.auto_completion(jids, '') + return the_input.auto_completion(jids, '', quotify=False) - def command_accept(self, args): + def command_accept(self, arg): """ Accept a JID from in roster. Authorize it AND subscribe to it """ - args = args.split() - if not args: + if not arg: item = self.roster_win.selected_row if isinstance(item, Contact): jid = item.bare_jid @@ -2023,7 +2106,7 @@ class RosterInfoTab(Tab): self.core.information('No subscription to accept') return else: - jid = JID(args[0]).bare + jid = JID(arg).bare contact = roster[jid] if contact is None: return @@ -2159,6 +2242,67 @@ class RosterInfoTab(Tab): selected_row.toggle_folded() return True + def get_contact_version(self): + """ + Show the versions of the resource(s) currently selected + """ + selected_row = self.roster_win.get_selected_row() + if isinstance(selected_row, Contact): + for resource in selected_row.resources: + self.core.command_version(str(resource.jid)) + elif isinstance(selected_row, Resource): + self.core.command_version(str(selected_row.jid)) + else: + self.core.information('Nothing to get versions from', 'Info') + + def show_contact_info(self): + """ + Show the contact info (resource number, status, presence, etc) + when 'i' is pressed. + """ + selected_row = self.roster_win.get_selected_row() + if isinstance(selected_row, Contact): + cont = selected_row + res = selected_row.get_highest_priority_resource() + msg = 'Contact: %s (%s)\n%s connected resource%s\nCurrent status: %s' % ( + cont.bare_jid, + res.presence if res else 'unavailable', + len(cont), + '' if len(cont) == 1 else 's', + res.status if res else '',) + elif isinstance(selected_row, Resource): + res = selected_row + msg = 'Resource: %s (%s)\nCurrent status: %s' % ( + res.jid, + res.presence, + res.status,) + elif isinstance(selected_row, RosterGroup): + rg = selected_row + msg = 'Group: %s [%s/%s] contacts online' % ( + rg.name, + rg.get_nb_connected_contacts(), + len(rg),) + else: + msg = None + if msg: + self.core.information(msg, 'Info') + + def change_contact_name(self): + """ + Auto-fill a /name command when 'n' is pressed + """ + selected_row = self.roster_win.get_selected_row() + if isinstance(selected_row, Contact): + jid = selected_row.bare_jid + elif isinstance(selected_row, Resource): + jid = JID(selected_row.jid).bare + else: + return + self.on_slash() + self.input.text = '/name "%s" ' % jid + self.input.key_end() + self.input.refresh() + def start_search(self): """ Start the search. The input should appear with a short instruction @@ -2347,6 +2491,14 @@ class ConversationTab(ChatTab): def get_name(self): return self.name + def get_nick(self): + jid = JID(self.name) + contact = roster[jid.bare] + if contact: + return contact.name or jid.user + else: + return jid.user + def on_input(self, key, raw): if not raw and key in self.key_func: self.key_func[key]() diff --git a/src/windows.py b/src/windows.py index 49e23a46..b0780cf5 100644 --- a/src/windows.py +++ b/src/windows.py @@ -319,15 +319,24 @@ class GlobalInfoBar(Win): self._win.erase() self.addstr(0, 0, "[", to_curses_attr(get_theme().COLOR_INFORMATION_BAR)) sorted_tabs = sorted(self.core.tabs, key=comp) + show_names = config.getl('show_tab_names', 'false') == 'true' + show_nums = config.getl('show_tab_numbers', 'true') != 'false' + use_nicks = config.getl('use_tab_nicks', 'true') != 'false' for tab in sorted_tabs: color = tab.color if config.get('show_inactive_tabs', 'true') == 'false' and\ color is get_theme().COLOR_TAB_NORMAL: continue try: - self.addstr("%s" % str(tab.nb), to_curses_attr(color)) - if config.get('show_tab_names', 'false') == 'true': - self.addstr(" %s" % str(tab.get_name()), to_curses_attr(color)) + if show_nums or not show_names: + self.addstr("%s" % str(tab.nb), to_curses_attr(color)) + if show_names: + self.addstr(' ', to_curses_attr(color)) + if show_names: + if use_nicks: + self.addstr("%s" % str(tab.get_nick()), to_curses_attr(color)) + else: + self.addstr("%s" % str(tab.get_name()), to_curses_attr(color)) self.addstr("|", to_curses_attr(get_theme().COLOR_INFORMATION_BAR)) except: # end of line break @@ -356,6 +365,7 @@ class VerticalGlobalInfoBar(Win): sorted_tabs = [tab for tab in sorted_tabs if\ tab.vertical_color is not get_theme().COLOR_VERTICAL_TAB_NORMAL] nb_tabs = len(sorted_tabs) + use_nicks = config.getl('use_tab_nicks', 'true') != 'false' if nb_tabs >= height: for y, tab in enumerate(sorted_tabs): if tab.vertical_color == get_theme().COLOR_VERTICAL_TAB_CURRENT: @@ -372,7 +382,10 @@ class VerticalGlobalInfoBar(Win): color = tab.vertical_color self.addstr(y if config.get('vertical_tab_list_sort', 'desc') != 'asc' else height - y - 1, 0, "%2d" % tab.nb, to_curses_attr(get_theme().COLOR_VERTICAL_TAB_NUMBER)) self.addstr('.') - self.addnstr("%s" % tab.get_name(), width - 4, to_curses_attr(color)) + if use_nicks: + self.addnstr("%s" % tab.get_nick(), width - 4, to_curses_attr(color)) + else: + self.addnstr("%s" % tab.get_name(), width - 4, to_curses_attr(color)) self._win.attron(to_curses_attr(get_theme().COLOR_VERTICAL_SEPARATOR)) self._win.vline(0, width-1, curses.ACS_VLINE, height) self._win.attroff(to_curses_attr(get_theme().COLOR_VERTICAL_SEPARATOR)) @@ -1077,8 +1090,7 @@ class Input(Win): completion_type = config.get('completion', 'normal') if quotify: for i, word in enumerate(word_list[:]): - if ' ' in word: - word_list[i] = '"' + word + '"' + word_list[i] = '"' + word + '"' if completion_type == 'shell' and self.text != '': self.shell_completion(word_list, add_after) else: @@ -1619,7 +1631,9 @@ class RosterWin(Win): presence = resource.presence nb = ' (%s)' % len(contact) color = RosterWin.color_show[presence]() - if contact.name: + if config.getl('show_roster_jids', 'true') == 'false' and contact.name: + display_name = '%s %s' % (contact.name, nb[1:]) + elif contact.name: display_name = '%s (%s)%s' % (contact.name, contact.bare_jid, nb,) else: @@ -1681,6 +1695,8 @@ class ContactInfoWin(Win): else: self.addstr('Ask: %s' % (contact.ask,)) self.finish_line() + if resource: + self.addstr(2, 0, 'Status: %s' % (resource.status)) def draw_group_info(self, group): |