From c0c0b16218aca64785307a22114f0212b3758534 Mon Sep 17 00:00:00 2001 From: mathieui Date: Thu, 10 May 2012 13:22:37 +0200 Subject: Fixes #2358 --- src/tabs.py | 1 + 1 file changed, 1 insertion(+) (limited to 'src') diff --git a/src/tabs.py b/src/tabs.py index eecdaac2..a34c1a37 100644 --- a/src/tabs.py +++ b/src/tabs.py @@ -808,6 +808,7 @@ class MucTab(ChatTab): /part [msg] """ arg = arg.strip() + msg = None if self.joined: self.disconnect() muc.leave_groupchat(self.core.xmpp, self.name, self.own_nick, arg) -- cgit v1.2.3 From 5498ad37c8e1235863040eeaa854ac5d3e6cfc89 Mon Sep 17 00:00:00 2001 From: mathieui Date: Thu, 10 May 2012 18:22:10 +0200 Subject: Prevent iq errors & timeouts on /join completion --- src/core.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/core.py b/src/core.py index 9b704682..94c1af5a 100644 --- a/src/core.py +++ b/src/core.py @@ -1714,7 +1714,10 @@ class Core(object): if jid.resource or jid.full.endswith('/'): # we are writing the resource: complete the node if not the_input.last_completion: - response = self.xmpp.plugin['xep_0030'].get_items(jid=jid.server, block=True, timeout=1) + try: + response = self.xmpp.plugin['xep_0030'].get_items(jid=jid.server, block=True, timeout=1) + except: + response = None if response: items = response['disco_items'].get_items() else: -- cgit v1.2.3 From 28c15a889e006769d343e729b55de66df3a00526 Mon Sep 17 00:00:00 2001 From: mathieui Date: Sat, 12 May 2012 20:44:38 +0200 Subject: Add the ignore_private and private_auto_response options MUC-specific options. private_auto_response is empty by default. + new event ignored_private --- src/core.py | 14 ++++++++++---- src/events.py | 6 +++++- 2 files changed, 15 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/core.py b/src/core.py index 94c1af5a..7fad4a63 100644 --- a/src/core.py +++ b/src/core.py @@ -842,13 +842,19 @@ class Core(object): room_from = jid.bare body = xhtml.get_body_from_message_stanza(message) tab = self.get_tab_by_name(jid.full, tabs.PrivateTab) # get the tab with the private conversation + ignore = config.get_by_tabname('ignore_private', 'false', + room_from).lower() == 'true' if not tab: # It's the first message we receive: create the tab - if body: + if body and not ignore: tab = self.open_private_window(room_from, nick_from, False) - if not tab: - return + if ignore: + self.events.trigger('ignored_private', message, tab) + msg = config.get_by_tabname('private_auto_response', None, room_from) + if msg and body: + self.xmpp.send_message(mto=jid.full, mbody=msg, mtype='chat') + return self.events.trigger('private_msg', message, tab) - if not body: + if not body or not tab: return tab.add_message(body, time=None, nickname=nick_from, forced_user=self.get_tab_by_name(room_from, tabs.MucTab).get_user_by_name(nick_from)) diff --git a/src/events.py b/src/events.py index 8def6cb0..e66c5ee5 100644 --- a/src/events.py +++ b/src/events.py @@ -40,6 +40,7 @@ class EventHandler(object): 'muc_nickchange': [], 'muc_ban': [], 'send_normal_presence': [], + 'ignored_private': [], } def add_event_handler(self, name, callback, position=0): @@ -63,7 +64,10 @@ class EventHandler(object): """ Call all the callbacks associated to the given event name. """ - callbacks = self.events[name] + callbacks = self.events.get(name, None) + if callbacks is None: + log.debug('%s: No such event.', name) + return for callback in callbacks: callback(*args, **kwargs) -- cgit v1.2.3 From ecc40fdc5efc873f87ff10f313f3045dbdf70363 Mon Sep 17 00:00:00 2001 From: mathieui Date: Sun, 13 May 2012 19:01:27 +0200 Subject: Catch a possible exception when trying to retrieve the rgb value in curses Fixes #2354 --- src/xhtml.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/xhtml.py b/src/xhtml.py index cf7a5fc0..38ec690c 100644 --- a/src/xhtml.py +++ b/src/xhtml.py @@ -206,7 +206,10 @@ def ncurses_color_to_html(color): html color. """ if color <= 15: - (r, g, b) = curses.color_content(color) + try: + (r, g, b) = curses.color_content(color) + except: # fallback in faulty terminals (e.g. xterm) + (r, g, b) = curses.color_content(color%8) r = r / 1000 * 6 - 0.01 g = g / 1000 * 6 - 0.01 b = b / 1000 * 6 - 0.01 -- cgit v1.2.3 From e86ecb3a529f30123df376ca44da932288b24831 Mon Sep 17 00:00:00 2001 From: mathieui Date: Mon, 14 May 2012 00:01:19 +0200 Subject: =?UTF-8?q?Fix=20the=20=E2=80=9C/bookmark{,=5Flocal}=20*=E2=80=9D?= =?UTF-8?q?=20behaviour?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/core.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/core.py b/src/core.py index 7fad4a63..140a3bb9 100644 --- a/src/core.py +++ b/src/core.py @@ -1935,6 +1935,7 @@ class Core(object): else: b.method = "local" bookmark.save_local() + bookmark.save_remote(self.xmpp) self.information('Bookmarks added and saved.', 'Info') return else: @@ -1990,11 +1991,13 @@ class Core(object): if isinstance(tab, tabs.MucTab): b = bookmark.get_by_jid(tab.get_name()) if not b: - b = bookmark.Bookmark(tab.get_name(), autojoin=autojoin) + b = bookmark.Bookmark(tab.get_name(), autojoin=autojoin, + method=bookmark.preferred) bookmark.bookmarks.append(b) else: - b.method = "local" + b.method = bookmark.preferred if bookmark.save_remote(self.xmpp, self): + bookmark.save_local() self.information("Bookmarks added.", "Info") else: self.information("Could not add the bookmarks.", "Info") -- cgit v1.2.3 From 58a41fe0652021481ebfa96fb3d238cddd93ca95 Mon Sep 17 00:00:00 2001 From: mathieui Date: Wed, 16 May 2012 01:59:32 +0200 Subject: Add status code handling - Fixes #2338 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Get status codes in presence and message stanzas, and show information related to them. If the change hinders privacy (logs added, or public JIDs), then a red “Warning” message is shown instead of the classic “Info”. --- src/core.py | 43 +++++++++++++++++++++++++++++++++++++++++++ src/tabs.py | 22 ++++++++++++++++++++++ 2 files changed, 65 insertions(+) (limited to 'src') diff --git a/src/core.py b/src/core.py index 140a3bb9..c2624584 100644 --- a/src/core.py +++ b/src/core.py @@ -246,6 +246,7 @@ class Core(object): self.xmpp.add_event_handler("groupchat_message", self.on_groupchat_message) self.xmpp.add_event_handler("groupchat_invite", self.on_groupchat_invite) self.xmpp.add_event_handler("groupchat_decline", self.on_groupchat_decline) + self.xmpp.add_event_handler("groupchat_config_status", self.on_status_codes) self.xmpp.add_event_handler("groupchat_subject", self.on_groupchat_subject) self.xmpp.add_event_handler("message", self.on_message) self.xmpp.add_event_handler("got_online" , self.on_got_online) @@ -1412,6 +1413,48 @@ class Core(object): if config.get_by_tabname('disable_beep', 'false', room_from, False).lower() != 'true': curses.beep() + def on_status_codes(self, message): + """ + Handle groupchat messages with status codes. + Those are received when a room configuration change occurs. + """ + room_from = message['from'] + tab = self.get_tab_by_name(room_from, tabs.MucTab) + status_codes = set([s.attrib['code'] for s in message.findall('{%s}x/{%s}status' % (tabs.NS_MUC_USER, tabs.NS_MUC_USER))]) + if '101' in status_codes: + self.information('Your affiliation in the room %s changed' % room_from, 'Info') + elif tab and status_codes: + show_unavailable = '102' in status_codes + hide_unavailable = '103' in status_codes + non_priv = '104' in status_codes + logging_on = '170' in status_codes + logging_off= '171' in status_codes + non_anon = '172' in status_codes + semi_anon = '173' in status_codes + full_anon = '174' in status_codes + modif = False + if show_unavailable or hide_unavailable or non_priv or logging_off\ + or non_anon or semi_anon or full_anon: + tab.add_message('\x19%(info_col)s}Info: A configuration change not privacy-related occured.' % {'info_col': get_theme().COLOR_INFORMATION_TEXT[0]}) + modif = True + if show_unavailable: + tab.add_message('\x19%(info_col)s}Info: The unavailable members are now shown.' % {'info_col': get_theme().COLOR_INFORMATION_TEXT[0]}) + elif hide_unavailable: + tab.add_message('\x19%(info_col)s}Info: The unavailable members are now hidden.' % {'info_col': get_theme().COLOR_INFORMATION_TEXT[0]}) + if non_anon: + tab.add_message('\x191}Warning:\x19%(info_col)s} The room is now not anonymous. (public JID)' % {'info_col': get_theme().COLOR_INFORMATION_TEXT[0]}) + elif semi_anon: + tab.add_message('\x19%(info_col)s}Info: The room is now semi-anonymous. (moderators-only JID)' % {'info_col': get_theme().COLOR_INFORMATION_TEXT[0]}) + elif full_anon: + tab.add_message('\x19%(info_col)s}Info: The room is now fully anonymous.' % {'info_col': get_theme().COLOR_INFORMATION_TEXT[0]}) + if logging_on: + tab.add_message('\x191}Warning: \x19%(info_col)s}This room is publicly logged' % {'info_col': get_theme().COLOR_INFORMATION_TEXT[0]}) + elif logging_off: + tab.add_message('\x19%(info_col)s}Info: This room is not logged anymore.' % {'info_col': get_theme().COLOR_INFORMATION_TEXT[0]}) + if modif: + self.refresh_window() + + def add_message_to_text_buffer(self, buff, txt, time=None, nickname=None, history=None): """ Add the message to the room if possible, else, add it to the Info window diff --git a/src/tabs.py b/src/tabs.py index a34c1a37..f798df69 100644 --- a/src/tabs.py +++ b/src/tabs.py @@ -1180,8 +1180,12 @@ class MucTab(ChatTab): self.send_chat_state('active') new_user.color = get_theme().COLOR_OWN_NICK self.add_message(_("\x19%(info_col)s}Your nickname is \x193}%(nick)s") % {'nick': from_nick, 'info_col': get_theme().COLOR_INFORMATION_TEXT[0]}) + if '201' in status_codes: + self.add_message('\x19%(info_col)s}Info: The room has been created' % {'info_col': get_theme().COLOR_INFORMATION_TEXT[0]}) if '170' in status_codes: self.add_message('\x191}Warning: \x19%(info_col)s}this room is publicly logged' % {'info_col': get_theme().COLOR_INFORMATION_TEXT[0]}) + if '100' in status_codes: + self.add_message('\x191}Warning: \x19%(info_col)s}This room is not anonymous.' % {'info_col': get_theme().COLOR_INFORMATION_TEXT[0]}) if self.core.current_tab() is not self: self.refresh_tab_win() self.core.current_tab().input.refresh() @@ -1191,6 +1195,8 @@ class MucTab(ChatTab): change_nick = '303' in status_codes kick = '307' in status_codes and typ == 'unavailable' ban = '301' in status_codes and typ == 'unavailable' + shutdown = '332' in status_codes and typ == 'unavailable' + non_member = '322' in status_codes and typ == 'unavailable' user = self.get_user_by_name(from_nick) # New user if not user: @@ -1209,6 +1215,12 @@ class MucTab(ChatTab): self.core.events.trigger('muc_kick', presence, self) self.core.on_user_left_private_conversation(from_room, from_nick, status) self.on_user_kicked(presence, user, from_nick) + elif shutdown: + self.core.events.trigger('muc_shutdown', presence, self) + self.on_muc_shutdown() + elif non_member: + self.core.events.trigger('muc_shutdown', presence, self) + self.on_non_member_kick() # user quit elif typ == 'unavailable': self.on_user_leave_groupchat(user, jid, status, from_nick, from_room) @@ -1222,6 +1234,16 @@ class MucTab(ChatTab): self.input.refresh() self.core.doupdate() + def on_non_member_kicked(self): + """We have been kicked because the MUC is members-only""" + self.add_message('\x19%(info_col)s}%You have been kicked because you are not a member and the room is now members-only.' % {'info_col': get_theme().COLOR_INFORMATION_TEXT[0]}) + self.disconnect() + + def on_muc_shutdown(self): + """We have been kicked because the MUC service is shutting down""" + self.add_message('\x19%(info_col)s}%You have been kicked because the MUC service is shutting down.' % {'info_col': get_theme().COLOR_INFORMATION_TEXT[0]}) + self.disconnect() + def on_user_join(self, from_nick, affiliation, show, status, role, jid): """ When a new user joins the groupchat -- cgit v1.2.3 From c04f0e97837dfc0c73defa3aaeada5c3848e2c59 Mon Sep 17 00:00:00 2001 From: mathieui Date: Wed, 16 May 2012 02:01:50 +0200 Subject: =?UTF-8?q?Prevent=20the=20rooms=20from=20going=20=E2=80=9Coffline?= =?UTF-8?q?=E2=80=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a blacklist inside the roster that contains the bare JIDs of all the rooms ever joined in this session, so that no JID using this server will ever be shown as getting “offline”. If there is a cleaner way to do that (discriminating JIDs), I welcome it. --- src/core.py | 5 +++++ src/roster.py | 6 +++++- src/tabs.py | 1 + 3 files changed, 11 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/core.py b/src/core.py index c2624584..a89e7b25 100644 --- a/src/core.py +++ b/src/core.py @@ -658,12 +658,17 @@ class Core(object): self.add_tab(form_tab, True) def on_got_offline(self, presence): + """ + A JID got offline + """ jid = presence['from'] logger.log_roster_change(jid.bare, 'got offline') # If a resource got offline, display the message in the conversation with this # precise resource. if jid.resource: self.add_information_message_to_conversation_tab(jid.full, '\x195}%s is \x191}offline' % (jid.full)) + if jid.server in roster.blacklist: + return self.add_information_message_to_conversation_tab(jid.bare, '\x195}%s is \x191}offline' % (jid.bare)) self.information('\x193}%s \x195}is \x191}offline' % (jid.bare), 'Roster') if isinstance(self.current_tab(), tabs.RosterInfoTab): diff --git a/src/roster.py b/src/roster.py index 7f93c4b2..e1251024 100644 --- a/src/roster.py +++ b/src/roster.py @@ -19,6 +19,10 @@ from sleekxmpp.xmlstream.stanzabase import JID from sleekxmpp.exceptions import IqError class Roster(object): + + # MUC domains to blacklist from the contacts roster + blacklist = set() + def __init__(self): """ node: the RosterSingle from SleekXMPP @@ -103,7 +107,7 @@ class Roster(object): def jids(self): """List of the contact JIDS""" - return [key for key in self.__node.keys() if key not in self.__mucs and key != self.jid] + return [key for key in self.__node.keys() if JID(key).server not in self.blacklist and key != self.jid] def get_contacts(self): """ diff --git a/src/tabs.py b/src/tabs.py index f798df69..be8085e8 100644 --- a/src/tabs.py +++ b/src/tabs.py @@ -1171,6 +1171,7 @@ class MucTab(ChatTab): self.core.events.trigger('muc_join', presence, self) if from_nick == self.own_nick: self.joined = True + roster.blacklist.add(JID(from_room).server) if self.get_name() in self.core.initial_joins: self.core.initial_joins.remove(self.get_name()) self._state = 'normal' -- cgit v1.2.3 From 1e07cd4f58a2c0c9bc29bb9f5d2e62d4b2366f74 Mon Sep 17 00:00:00 2001 From: mathieui Date: Wed, 16 May 2012 17:22:13 +0200 Subject: Put a space after completion only if there is one (and only one) command --- src/tabs.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/tabs.py b/src/tabs.py index be8085e8..1ac301a1 100644 --- a/src/tabs.py +++ b/src/tabs.py @@ -216,11 +216,8 @@ class Tab(object): # one possibily. The next tab will complete the argument. # Otherwise we would need to add a useless space before being # able to complete the arguments. - hit_copy = the_input.hit_list[:] - for w in hit_copy[:]: - while hit_copy.count(w) > 1: - hit_copy.remove(w) - if len(hit_copy) in (1, 0): + hit_copy = set(the_input.hit_list) + if len(hit_copy) == 1: the_input.do_command(' ') return True return False -- cgit v1.2.3 From 155914470da7f63d7a41025a17c1fd0827ed7df9 Mon Sep 17 00:00:00 2001 From: mathieui Date: Wed, 16 May 2012 17:39:14 +0200 Subject: Complete the commands differently. If there is 0 match for the beginning of command, delete the last letter (over and over) until there is a match, then complete that. --- src/tabs.py | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'src') diff --git a/src/tabs.py b/src/tabs.py index 1ac301a1..a66d81cd 100644 --- a/src/tabs.py +++ b/src/tabs.py @@ -217,6 +217,10 @@ class Tab(object): # Otherwise we would need to add a useless space before being # able to complete the arguments. hit_copy = set(the_input.hit_list) + while not hit_copy: + the_input.key_backspace() + the_input.auto_completion(words, '', quotify=False) + hit_copy = set(the_input.hit_list) if len(hit_copy) == 1: the_input.do_command(' ') return True -- cgit v1.2.3 From da30c8c79f5950ef85296ba8f749b929b1bbbd57 Mon Sep 17 00:00:00 2001 From: mathieui Date: Wed, 16 May 2012 19:50:56 +0200 Subject: Put color in the topic again --- src/core.py | 9 +++++++-- src/tabs.py | 3 ++- 2 files changed, 9 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/core.py b/src/core.py index a89e7b25..ee7fcf2f 100644 --- a/src/core.py +++ b/src/core.py @@ -1361,9 +1361,14 @@ class Core(object): if not subject or not tab: return if nick_from: - self.add_message_to_text_buffer(tab._text_buffer, _("%(nick)s set the subject to: %(subject)s") % {'nick':nick_from, 'subject':subject}, time=None) + self.add_message_to_text_buffer(tab._text_buffer, + _("\x19%(info_col)s}%(nick)s set the subject to: %(subject)s") % + {'info_col': get_theme().COLOR_INFORMATION_TEXT[0], 'nick':nick_from, 'subject':subject}, + time=None) else: - self.add_message_to_text_buffer(tab._text_buffer, _("The subject is: %(subject)s") % {'subject':subject}, time=None) + self.add_message_to_text_buffer(tab._text_buffer, _("\x19%(info_col)s}The subject is: %(subject)s") % + {'subject':subject, 'info_col': get_theme().COLOR_INFORMATION_TEXT[0]}, + time=None) tab.topic = subject if self.get_tab_by_name(room_from, tabs.MucTab) is self.current_tab(): self.refresh_window() diff --git a/src/tabs.py b/src/tabs.py index a66d81cd..39b81df7 100644 --- a/src/tabs.py +++ b/src/tabs.py @@ -853,7 +853,8 @@ class MucTab(ChatTab): /topic [new topic] """ if not arg.strip(): - self._text_buffer.add_message(_("The subject of the room is: %s") % self.topic) + self._text_buffer.add_message(_("\x19%s}The subject of the room is: %s") % + (get_theme().COLOR_INFORMATION_TEXT[0], self.topic)) self.text_win.refresh() self.input.refresh() return -- cgit v1.2.3 From 0f7bda20b8b89bf334d67da45f4fc3e4dc8117f9 Mon Sep 17 00:00:00 2001 From: mathieui Date: Thu, 17 May 2012 01:00:35 +0200 Subject: Add a way to review room highlights - Fixes #1673 This new features is available with M-p and M-n (previous/next). It saves the last highlight viewed, meaning that if you scroll in the buffer, M-n or M-p will take you to the next or previous hl compared to the one before you started scrolling. For convenience, going to the previous highlight of the first highlight will take you to the bottom of the buffer, and going to the next highlight of the last highlight will do *the same*. If there are several highlights in one message, only the first line will be considered a highlight. --- src/tabs.py | 20 +++++++++++++- src/text_buffer.py | 4 +-- src/windows.py | 81 +++++++++++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 98 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/tabs.py b/src/tabs.py index 39b81df7..f97c7f03 100644 --- a/src/tabs.py +++ b/src/tabs.py @@ -600,6 +600,8 @@ class MucTab(ChatTab): self.key_func['^I'] = self.completion self.key_func['M-u'] = self.scroll_user_list_down self.key_func['M-y'] = self.scroll_user_list_up + self.key_func['M-n'] = self.go_to_next_hl + self.key_func['M-p'] = self.go_to_prev_hl # commands self.commands['ignore'] = (self.command_ignore, _("Usage: /ignore \nIgnore: Ignore a specified nickname."), self.completion_ignore) self.commands['unignore'] = (self.command_unignore, _("Usage: /unignore \nUnignore: Remove the specified nickname from the ignore list."), self.completion_unignore) @@ -630,6 +632,22 @@ class MucTab(ChatTab): def general_jid(self): return self.get_name() + def go_to_next_hl(self): + """ + Go to the next HL in the room, or the last + """ + self.text_win.next_highlight() + self.refresh() + self.core.doupdate() + + def go_to_prev_hl(self): + """ + Go to the previous HL in the room, or the first + """ + self.text_win.previous_highlight() + self.refresh() + self.core.doupdate() + def completion_version(self, the_input): """Completion for /version""" compare_users = lambda x: x.last_talked @@ -1502,7 +1520,7 @@ class MucTab(ChatTab): if highlight: nick_color = highlight time = time or datetime.now() - self._text_buffer.add_message(txt, time, nickname, nick_color, history, user) + self._text_buffer.add_message(txt, time, nickname, nick_color, history, user, highlight=highlight) return highlight class PrivateTab(ChatTab): diff --git a/src/text_buffer.py b/src/text_buffer.py index 9b717882..2d83ab7e 100644 --- a/src/text_buffer.py +++ b/src/text_buffer.py @@ -35,7 +35,7 @@ class TextBuffer(object): def add_window(self, win): self.windows.append(win) - def add_message(self, txt, time=None, nickname=None, nick_color=None, history=None, user=None): + def add_message(self, txt, time=None, nickname=None, nick_color=None, history=None, user=None, highlight=False): time = time or datetime.now() if txt.startswith('/me '): if nick_color: @@ -57,7 +57,7 @@ class TextBuffer(object): ret_val = None for window in self.windows: # make the associated windows # build the lines from the new message - nb = window.build_new_message(msg, history=history) + nb = window.build_new_message(msg, history=history, highlight=highlight) if ret_val is None: ret_val = nb if window.pos != 0: diff --git a/src/windows.py b/src/windows.py index 7185346e..ae79fb74 100644 --- a/src/windows.py +++ b/src/windows.py @@ -622,6 +622,8 @@ class TextWin(Win): # on resize, we rebuild all the messages self.lock = False self.lock_buffer = [] + self.highlights = [] + self.hl_pos = -1 def toggle_lock(self): if self.lock: @@ -637,6 +639,74 @@ class TextWin(Win): self.built_lines.append(line) self.lock = False + def next_highlight(self): + """ + Go to the next highlight in the buffer. + (depending on which highlight was selected before) + if the buffer is already positionned on the last, of if there are no + highlights, scroll to the end of the buffer. + """ + log.debug('Going to the next highlight…') + if not self.highlights or self.hl_pos == -1 or \ + self.hl_pos == len(self.highlights)-1: + self.hl_pos = -1 + self.pos = 0 + return + hl_size = len(self.highlights) - 1 + if self.hl_pos < hl_size: + self.hl_pos += 1 + else: + self.hl_pos = hl_size + + hl = self.highlights[self.hl_pos] + pos = None + while not pos: + try: + pos = self.built_lines.index(hl) + except ValueError: + self.highlights = self.highlights[self.hl_pos+1:] + if not self.highlights: + self.hl_pos = -1 + self.pos = 0 + return + hl = self.highlights[0] + self.pos = len(self.built_lines) - pos - self.height + if self.pos < 0 or self.pos >= len(self.built_lines): + self.pos = 0 + + def previous_highlight(self): + """ + Go to the previous highlight in the buffer. + (depending on which highlight was selected before) + if the buffer is already positionned on the first, or if there are no + highlights, scroll to the end of the buffer. + """ + log.debug('Going to the previous highlight…') + if not self.highlights or self.hl_pos == 0: + self.hl_pos = -1 + self.pos = 0 + return + if self.hl_pos < 0: + self.hl_pos = len(self.highlights) - 1 + elif self.hl_pos > 0: + self.hl_pos -= 1 + + hl = self.highlights[self.hl_pos] + pos = None + while not pos: + try: + pos = self.built_lines.index(hl) + except ValueError: + self.highlights = self.highlights[self.hl_pos+1:] + if not self.highlights: + self.hl_pos = -1 + self.pos = 0 + return + hl = self.highlights[0] + self.pos = len(self.built_lines) - pos - self.height + if self.pos < 0 or self.pos >= len(self.built_lines): + self.pos = 0 + def scroll_up(self, dist=14): self.pos += dist if self.pos + self.height > len(self.built_lines): @@ -676,7 +746,7 @@ class TextWin(Win): if None not in self.built_lines: self.built_lines.append(None) - def build_new_message(self, message, history=None, clean=True): + def build_new_message(self, message, history=None, clean=True, highlight=False): """ Take one message, build it and add it to the list Return the number of lines that are built for the given @@ -703,10 +773,13 @@ class TextWin(Win): start_pos=line[0], end_pos=line[1])) else: + for line in lines: - self.built_lines.append(Line(msg=message, - start_pos=line[0], - end_pos=line[1])) + saved_line = Line(msg=message,start_pos=line[0],end_pos=line[1]) + self.built_lines.append(saved_line) + if highlight: + highlight = False + self.highlights.append(saved_line) if clean: while len(self.built_lines) > self.lines_nb_limit: self.built_lines.pop(0) -- cgit v1.2.3 From 4c0a3fb5a2eca27133e558fa8394b3d07d0203ba Mon Sep 17 00:00:00 2001 From: mathieui Date: Thu, 17 May 2012 03:34:04 +0200 Subject: Resolves separator persistence problems - Fixes #2073 Now we have to pass the textbuffer object when we want to add a line separator. --- src/tabs.py | 8 ++++---- src/windows.py | 15 ++++++++++++++- 2 files changed, 18 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/tabs.py b/src/tabs.py index f97c7f03..be2a886d 100644 --- a/src/tabs.py +++ b/src/tabs.py @@ -535,7 +535,7 @@ class ChatTab(Tab): def move_separator(self): self.text_win.remove_line_separator() - self.text_win.add_line_separator() + self.text_win.add_line_separator(self._text_buffer) self.text_win.refresh() self.input.refresh() @@ -1150,7 +1150,7 @@ class MucTab(ChatTab): else: self.state = 'disconnected' self.text_win.remove_line_separator() - self.text_win.add_line_separator() + self.text_win.add_line_separator(self._text_buffer) if config.get_by_tabname('send_chat_states', 'true', self.general_jid, True) == 'true' and not self.input.get_text(): self.send_chat_state('inactive') @@ -1686,7 +1686,7 @@ class PrivateTab(ChatTab): def on_lose_focus(self): self.state = 'normal' self.text_win.remove_line_separator() - self.text_win.add_line_separator() + self.text_win.add_line_separator(self._text_buffer) tab = self.core.get_tab_by_name(JID(self.name).bare, MucTab) if tab and tab.joined and config.get_by_tabname( 'send_chat_states', 'true', self.general_jid, True) == 'true'\ @@ -2604,7 +2604,7 @@ class ConversationTab(ChatTab): resource = None self.state = 'normal' self.text_win.remove_line_separator() - self.text_win.add_line_separator() + self.text_win.add_line_separator(self._text_buffer) if config.get_by_tabname('send_chat_states', 'true', self.general_jid, True) == 'true' and (not self.input.get_text() or not self.input.get_text().startswith('//')): if resource: self.send_chat_state('inactive') diff --git a/src/windows.py b/src/windows.py index ae79fb74..ea2cb3f7 100644 --- a/src/windows.py +++ b/src/windows.py @@ -620,11 +620,17 @@ class TextWin(Win): self.pos = 0 self.built_lines = [] # Each new message is built and kept here. # on resize, we rebuild all the messages + self.lock = False self.lock_buffer = [] + + # the Lines of the highlights in that buffer self.highlights = [] + # the current HL position in that list self.hl_pos = -1 + self.separator_after = None + def toggle_lock(self): if self.lock: self.release_lock() @@ -738,13 +744,18 @@ class TextWin(Win): log.debug('remove_line_separator') if None in self.built_lines: self.built_lines.remove(None) + self.separator_after = None - def add_line_separator(self): + def add_line_separator(self, room=None): """ add a line separator at the end of messages list + room is a textbuffer that is needed to get the previous message + (in case of resize) """ if None not in self.built_lines: self.built_lines.append(None) + if room: + self.separator_after = room.messages[-1] def build_new_message(self, message, history=None, clean=True, highlight=False): """ @@ -862,6 +873,8 @@ class TextWin(Win): self.built_lines = [] for message in room.messages: self.build_new_message(message, clean=False) + if self.separator_after is message: + self.build_new_message(None) while len(self.built_lines) > self.lines_nb_limit: self.built_lines.pop(0) -- cgit v1.2.3 From 65062754e194e2c1fadf4a30677444730e73ec71 Mon Sep 17 00:00:00 2001 From: mathieui Date: Thu, 17 May 2012 14:11:02 +0200 Subject: Fix a crash if there are no messages in the room --- src/windows.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/windows.py b/src/windows.py index ea2cb3f7..e5973ec9 100644 --- a/src/windows.py +++ b/src/windows.py @@ -754,7 +754,7 @@ class TextWin(Win): """ if None not in self.built_lines: self.built_lines.append(None) - if room: + if room and room.messages: self.separator_after = room.messages[-1] def build_new_message(self, message, history=None, clean=True, highlight=False): -- cgit v1.2.3 From 64defba0ae18812196caadab26cdea0d24849be5 Mon Sep 17 00:00:00 2001 From: mathieui Date: Thu, 17 May 2012 16:45:40 +0200 Subject: Show subscription changes in the info buffer - Fixes #2234 --- src/core.py | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/src/core.py b/src/core.py index ee7fcf2f..3ac0a0b5 100644 --- a/src/core.py +++ b/src/core.py @@ -991,6 +991,8 @@ class Core(object): """subscribed received""" jid = presence['from'].bare contact = roster[jid] + if contact.subscription not in ('both', 'from'): + self.information('%s accepted your contact proposal' % jid, 'Roster') if contact.pending_out: contact.pending_out = False if isinstance(self.current_tab(), tabs.RosterInfoTab): @@ -1000,9 +1002,10 @@ class Core(object): """unsubscribe received""" jid = presence['from'].bare contact = roster[jid] - if contact.subscription in ('to', 'both'): - self.information('%s does not want to receive your status anymore.' % jid, 'Roster') - self.get_tab_by_number(0).state = 'highlight' + if not contact: + return + self.information('%s does not want to receive your status anymore.' % jid, 'Roster') + self.get_tab_by_number(0).state = 'highlight' if isinstance(self.current_tab(), tabs.RosterInfoTab): self.refresh_window() @@ -1010,13 +1013,14 @@ class Core(object): """unsubscribed received""" jid = presence['from'].bare contact = roster[jid] - if contact.subscription in ('both', 'from'): - self.information('%s does not want you to receive his status anymore.'%jid, 'Roster') - self.get_tab_by_number(0).state = 'highlight' - elif contact.pending_out: - self.information('%s rejected your contact proposal.' % jid, 'Roster') - self.get_tab_by_number(0).state = 'highlight' + if not contact: + return + if contact.pending_out: + self.information('%s rejected your contact proposal' % jid, 'Roster') contact.pending_out = False + else: + self.information('%s does not want you to receive his/her/its status anymore.'%jid, 'Roster') + self.get_tab_by_number(0).state = 'highlight' if isinstance(self.current_tab(), tabs.RosterInfoTab): self.refresh_window() -- cgit v1.2.3 From 0f8a5abdc0818b4071bf4623a12ee95223b11ba3 Mon Sep 17 00:00:00 2001 From: mathieui Date: Thu, 17 May 2012 16:55:31 +0200 Subject: Add an option to always show the separator - Fixes #2240 --- src/tabs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/tabs.py b/src/tabs.py index be2a886d..92b913b0 100644 --- a/src/tabs.py +++ b/src/tabs.py @@ -1156,7 +1156,7 @@ class MucTab(ChatTab): def on_gain_focus(self): self.state = 'current' - if self.text_win.built_lines and self.text_win.built_lines[-1] is None: + if self.text_win.built_lines and self.text_win.built_lines[-1] is None and config.getl('show_useless_separator', 'false') != 'true': self.text_win.remove_line_separator() curses.curs_set(1) if self.joined and config.get_by_tabname('send_chat_states', 'true', self.general_jid, True) == 'true' and not self.input.get_text(): -- cgit v1.2.3 From 3411d8ca83591adf9a92b3c1c78fbd74a4612fe7 Mon Sep 17 00:00:00 2001 From: mathieui Date: Thu, 17 May 2012 17:15:15 +0200 Subject: Add a shortcut to go to the first unread message (separator) with M-p --- src/tabs.py | 6 ++++++ src/windows.py | 6 +++--- 2 files changed, 9 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/tabs.py b/src/tabs.py index 92b913b0..61b322a0 100644 --- a/src/tabs.py +++ b/src/tabs.py @@ -394,6 +394,7 @@ class ChatTab(Tab): # since the last input self.remote_supports_attention = False self.key_func['M-v'] = self.move_separator + self.key_func['M-h'] = self.scroll_separator self.key_func['M-/'] = self.last_words_completion self.key_func['^M'] = self.on_enter self.commands['say'] = (self.command_say, @@ -566,6 +567,11 @@ class ChatTab(Tab): def on_half_scroll_down(self): self.text_win.scroll_down((self.text_win.height-1) // 2) + def scroll_separator(self): + self.text_win.scroll_to_separator() + self.refresh() + self.core.doupdate() + class MucTab(ChatTab): """ diff --git a/src/windows.py b/src/windows.py index e5973ec9..29bf5953 100644 --- a/src/windows.py +++ b/src/windows.py @@ -731,11 +731,11 @@ class TextWin(Win): present, scroll at the top of the window """ if None in self.built_lines: - self.pos = self.built_lines.index(None) + self.pos = len(self.built_lines) - self.built_lines.index(None) - self.height + 1 + if self.pos < 0: + self.pos = 0 # Chose a proper position (not too high) self.scroll_up(0) - else: # Go at the top of the win - self.pos = len(self.built_lines) - self.height def remove_line_separator(self): """ -- cgit v1.2.3 From fb450a71386d39afe4059e84632b03f6a230109c Mon Sep 17 00:00:00 2001 From: mathieui Date: Thu, 17 May 2012 17:28:53 +0200 Subject: Use a different theme variable for the /me message --- src/text_buffer.py | 2 +- src/theming.py | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/text_buffer.py b/src/text_buffer.py index 2d83ab7e..b615e96c 100644 --- a/src/text_buffer.py +++ b/src/text_buffer.py @@ -45,7 +45,7 @@ class TextBuffer(object): else: color = None # TODO: display the bg color too. - txt = ("\x19%(info_col)s}* \x19%(col)s}" % {'col':color or 5, 'info_col':get_theme().COLOR_INFORMATION_TEXT[0]})+ nickname + ' \x19%(info_col)s}' % {'info_col':get_theme().COLOR_INFORMATION_TEXT[0]} + txt[4:] + txt = '\x19%(info_col)s}* \x19%(col)s}%(nick)s \x19%(info_col)s}%(msg)s' % {'info_col':get_theme().COLOR_ME_MESSAGE[0], 'col': color or 5, 'nick': nickname, 'msg': txt[4:]} nickname = None msg = Message(txt='%s\x19o'%(txt.replace('\t', ' '),), nick_color=nick_color, time=time, str_time=time.strftime("%Y-%m-%d %H:%M:%S")\ diff --git a/src/theming.py b/src/theming.py index e45a25ff..94d7b005 100644 --- a/src/theming.py +++ b/src/theming.py @@ -108,6 +108,9 @@ class Theme(object): CHAR_AFFILIATION_MEMBER = '+' CHAR_AFFILIATION_NONE = '-' + # Color for the /me message + COLOR_ME_MESSAGE = (6, -1) + # Separators COLOR_VERTICAL_SEPARATOR = (4, -1) COLOR_NEW_TEXT_SEPARATOR = (2, -1) -- cgit v1.2.3 From c77e2878b891f000ba1c3a030acd0c195c7e1948 Mon Sep 17 00:00:00 2001 From: mathieui Date: Thu, 17 May 2012 20:48:46 +0200 Subject: =?UTF-8?q?Do=20not=20add=20a=20'=E2=80=A6'=20if=20the=20nick=20ha?= =?UTF-8?q?s=20the=20exact=20same=20size=20as=20the=20limit?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/windows.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/windows.py b/src/windows.py index 29bf5953..06214abb 100644 --- a/src/windows.py +++ b/src/windows.py @@ -51,7 +51,7 @@ def truncate_nick(nick, size=None): size = size or config.get('max_nick_length', 25) if size < 1: size = 1 - if nick and len(nick) >= size: + if nick and len(nick) > size: return nick[:size]+'…' return nick -- cgit v1.2.3 From 51c788ad96703d215942499ffefe6fdc98326b6b Mon Sep 17 00:00:00 2001 From: mathieui Date: Sat, 19 May 2012 22:28:30 +0200 Subject: Allow nick completion in the Private tabs as well. --- src/tabs.py | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/tabs.py b/src/tabs.py index 61b322a0..2126cef9 100644 --- a/src/tabs.py +++ b/src/tabs.py @@ -8,9 +8,9 @@ """ a Tab object is a way to organize various Windows (see windows.py) around the screen at once. -A tab is then composed of multiple Buffer. +A tab is then composed of multiple Buffers. Each Tab object has different refresh() and resize() methods, defining how its -Windows are displayed, resized, etc +Windows are displayed, resized, etc. """ MIN_WIDTH = 42 @@ -1566,7 +1566,26 @@ class PrivateTab(ChatTab): self.parent_muc.privates.remove(self) def completion(self): - self.complete_commands(self.input) + """ + Called when Tab is pressed, complete the nickname in the input + """ + if self.complete_commands(self.input): + return + + # If we are not completing a command or a command's argument, complete a nick + compare_users = lambda x: x.last_talked + word_list = [user.nick for user in sorted(self.parent_muc.users, key=compare_users, reverse=True)\ + if user.nick != self.own_nick] + after = config.get('after_completion', ',')+" " + input_pos = self.input.pos + self.input.line_pos + if ' ' not in self.input.get_text()[:input_pos] or (self.input.last_completion and\ + self.input.get_text()[:input_pos] == self.input.last_completion + after): + add_after = after + else: + add_after = '' + self.input.auto_completion(word_list, add_after, quotify=False) + empty_after = self.input.get_text() == '' or (self.input.get_text().startswith('/') and not self.input.get_text().startswith('//')) + self.send_composing_chat_state(empty_after) def command_say(self, line, attention=False): if not self.on: -- cgit v1.2.3