From c091e0c16fb1dbcf5c2a3789c8960e5384a480c0 Mon Sep 17 00:00:00 2001 From: "louiz@4325f9fc-e183-4c21-96ce-0ab188b42d13" Date: Wed, 27 Oct 2010 22:49:52 +0000 Subject: Command mode in roster tab, toggle offline contacts with 'o' and sort contacts by show --- src/contact.py | 10 ++++-- src/gui.py | 6 +--- src/multiuserchat.py | 4 --- src/roster.py | 87 +++++++++++++++++++++++++++++++++++++++++++--------- src/tab.py | 66 +++++++++++++++++++++++++++++++++------ src/user.py | 1 - src/window.py | 27 +++++++++++++--- 7 files changed, 159 insertions(+), 42 deletions(-) (limited to 'src') diff --git a/src/contact.py b/src/contact.py index 5fa0a17d..35a4a4a8 100644 --- a/src/contact.py +++ b/src/contact.py @@ -14,6 +14,11 @@ # You should have received a copy of the GNU General Public License # along with Poezio. If not, see . + +""" +Defines the Resource and Contact classes +""" + from sleekxmpp.xmlstream.stanzabase import JID class Resource(object): @@ -55,7 +60,7 @@ class Resource(object): class Contact(object): """ This a way to gather multiple resources from the same bare JID. - This class contains zero or more esource class and useful methods + This class contains zero or more Resource object and useful methods to get the resource with the highest priority, etc """ def __init__(self, bare_jid): @@ -116,6 +121,7 @@ class Contact(object): if resource.get_jid().full == fulljid: return resource return None + def toggle_folded(self): """ Fold if it's unfolded, and vice versa @@ -148,7 +154,7 @@ class Contact(object): def get_resources(self): """ - Return all resources + Return all resources, sorted by priority """ compare_resources = lambda x: x.get_priority() return sorted(self._resources, key=compare_resources) diff --git a/src/gui.py b/src/gui.py index ec7480bd..8e14ca35 100644 --- a/src/gui.py +++ b/src/gui.py @@ -171,8 +171,6 @@ class Gui(object): self.refresh_window() def on_got_offline(self, presence): - from common import debug - debug('OFFLINE: %s\n' % presence) jid = presence['from'] contact = self.roster.get_contact_by_jid(jid.bare) if not contact: @@ -185,8 +183,6 @@ class Gui(object): self.refresh_window() def on_got_online(self, presence): - from common import debug - debug('ONLINE: %s\n' % presence) jid = presence['from'] contact = self.roster.get_contact_by_jid(jid.bare) if not contact: @@ -537,7 +533,7 @@ class Gui(object): Resize the whole screen """ with resize_lock: - self.resize_timer = None + # self.resize_timer = None for tab in self.tabs: tab.resize(self.stdscr) self.refresh_window() diff --git a/src/multiuserchat.py b/src/multiuserchat.py index 5800e7f9..311bce87 100644 --- a/src/multiuserchat.py +++ b/src/multiuserchat.py @@ -22,9 +22,6 @@ import sleekxmpp from xml.etree import cElementTree as ET - -from common import debug - def send_private_message(xmpp, jid, line): """ Send a private message @@ -54,7 +51,6 @@ def change_show(xmpp, jid, own_nick, show, status): pres['type'] = show if status: pres['status'] = status - debug('Change presence: %s\n' % (pres)) pres.send() def change_subject(xmpp, jid, subject): diff --git a/src/roster.py b/src/roster.py index 03a5f93a..7191e5ca 100644 --- a/src/roster.py +++ b/src/roster.py @@ -14,6 +14,12 @@ # You should have received a copy of the GNU General Public License # along with Poezio. If not, see . + +""" +Defines the Roster and RosterGroup classes +""" + +from config import config from contact import Contact, Resource class Roster(object): @@ -29,9 +35,15 @@ class Roster(object): self._contacts[jid] = contact def get_contact_len(self): + """ + Return the number of contacts in this group + """ return len(self._contacts.keys()) def get_contact_by_jid(self, jid): + """ + Returns the contact with the given bare JID + """ if jid in self._contacts: return self._contacts[jid] return None @@ -42,15 +54,15 @@ class Roster(object): Add or remove RosterGroup if needed """ # add the contact to each group he is in - if not len(groups): + # If the contact hasn't any group, we put her in + # the virtual default 'none' group + if not len(groups): groups = ['none'] for group in groups: - if group in contact._groups: - continue - else: + if group not in contact._groups: # create the group if it doesn't exist yet contact._groups.append(group) - self.add_contact_to_group(group, contact) + self.add_contact_to_group(group, contact) # remove the contact from each group he is not in for group in contact._groups: if group not in groups: @@ -60,7 +72,7 @@ class Roster(object): def remove_contact_from_group(self, group_name, contact): """ Remove the contact from the group. - Remove also the group if this makes it empty + Delete the group if this makes it empty """ for group in self._roster_groups: if group.name == group_name: @@ -76,28 +88,40 @@ class Roster(object): """ for group in self._roster_groups: if group.name == group_name: - group.add_contact(contact) + if not group.has_contact(contact): + group.add_contact(contact) return new_group = RosterGroup(group_name) self._roster_groups.append(new_group) new_group.add_contact(contact) def get_groups(self): + """ + Returns the list of groups + """ return self._roster_groups def __len__(self): """ Return the number of line that would be printed + for the whole roster """ - l = 0 + length = 0 for group in self._roster_groups: - l += 1 + if group.get_nb_connected_contacts() == 0: + continue + length += 1 # One for the group's line itself if not group.folded: for contact in group.get_contacts(): - l += 1 + # We do not count the offline contacts (depending on config) + if config.get('roster_show_offline', 'false') == 'false' and\ + contact.get_nb_resources() == 0: + continue + length += 1 # One for the contact's line if not contact._folded: - l += contact.get_nb_resources() - return l + # One for each resource, if the contact is unfolded + length += contact.get_nb_resources() + return length def __repr__(self): ret = '== Roster:\nContacts:\n' @@ -108,6 +132,13 @@ class Roster(object): ret += '%s\n' % (group,) return ret + '\n' +PRESENCE_PRIORITY = {'unavailable': 0, + 'xa': 1, + 'away': 2, + 'dnd': 3, + '': 4, + 'available': 4} + class RosterGroup(object): """ A RosterGroup is a group containing contacts @@ -122,6 +153,15 @@ class RosterGroup(object): def is_empty(self): return len(self._contacts) == 0 + def has_contact(self, contact): + """ + Return a bool, telling if the contact + is already in the group + """ + if contact in self._contacts: + return True + return False + def remove_contact(self, contact): """ Remove a Contact object to the list @@ -139,10 +179,27 @@ class RosterGroup(object): self._contacts.append(contact) def get_contacts(self): - return self._contacts + def compare_contact(a): + if not a.get_highest_priority_resource(): + return 0 + show = a.get_highest_priority_resource().get_presence() + if show not in PRESENCE_PRIORITY: + return 5 + return PRESENCE_PRIORITY[show] + return sorted(self._contacts, key=compare_contact, reverse=True) + + def toggle_folded(self): + self.folded = not self.folded def __repr__(self): return '' % (self.name, self._contacts) - def toggle_folded(self): - self.folded = not self.folded + def __len__(self): + return len(self._contacts) + + def get_nb_connected_contacts(self): + l = 0 + for contact in self._contacts: + if contact.get_highest_priority_resource(): + l += 1 + return l diff --git a/src/tab.py b/src/tab.py index 35e71efe..04d55f80 100644 --- a/src/tab.py +++ b/src/tab.py @@ -28,6 +28,7 @@ MIN_HEIGHT = 16 import window import theme import curses +from config import config from roster import RosterGroup from contact import Contact, Resource @@ -357,7 +358,10 @@ class PrivateTab(Tab): def on_gain_focus(self): self._room.set_color_state(theme.COLOR_TAB_CURRENT) - curses.curs_set(1) + if not self.input.input_mode: + curses.curs_set(1) + else: + curses.curs_set(0) def on_scroll_up(self): self._room.scroll_up(self.text_win.height-1) @@ -382,6 +386,16 @@ class RosterInfoTab(Tab): A tab, splitted in two, containing the roster and infos """ def __init__(self, stdscr): + self.single_key_commands = { + "^J": self.on_enter, + "^M": self.on_enter, + "\n": self.on_enter, + ' ': self.on_space, + "/": self.on_slash, + "KEY_UP": self.move_cursor_up, + "KEY_DOWN": self.move_cursor_down, + "o": self.toggle_offline_show, + } Tab.__init__(self, stdscr) self.name = "Roster" roster_width = self.width//2 @@ -391,7 +405,7 @@ class RosterInfoTab(Tab): self.info_win = window.TextWin(self.height-2, info_width, 0, roster_width+1, stdscr, self.visible) self.roster_win = window.RosterWin(self.height-2-3, roster_width, 0, 0, stdscr, self.visible) self.contact_info_win = window.ContactInfoWin(3, roster_width, self.height-2-3, 0, stdscr, self.visible) - self.input = window.Input(1, self.width, self.height-1, 0, stdscr, self.visible) + self.input = window.Input(1, self.width, self.height-1, 0, stdscr, self.visible, False, "Enter commands with “/”. “o”: toggle offline show") self.set_color_state(theme.COLOR_TAB_NORMAL) def resize(self, stdscr): @@ -423,12 +437,34 @@ class RosterInfoTab(Tab): self._color_state = color def on_input(self, key): - if key in ('\n', '^J', '^M') and self.input.is_empty(): - return self.on_enter() - if key == ' ': - return self.on_space() - # In writting mode - # return self.input.do_command(key) + if self.input.input_mode: + ret = self.input.do_command(key) + # if the input is empty, go back to command mode + if self.input.is_empty(): + self.input.input_mode = False + curses.curs_set(0) + self.input.rewrite_text() + return ret + if key in self.single_key_commands: + return self.single_key_commands[key]() + + def toggle_offline_show(self): + """ + Show or hide offline contacts + """ + option = 'roster_show_offline' + if config.get(option, 'false') == 'false': + config.set_and_save(option, 'true') + else: + config.set_and_save(option, 'false') + return True + def on_slash(self): + """ + '/' is pressed, we enter "input mode" + """ + self.input.input_mode = True + curses.curs_set(1) + self.on_input("/") # we add the slash def on_lose_focus(self): self._color_state = theme.COLOR_TAB_NORMAL @@ -440,11 +476,21 @@ class RosterInfoTab(Tab): def add_message(self): return False - def on_scroll_down(self): + def move_cursor_down(self): self.roster_win.move_cursor_down() + return True - def on_scroll_up(self): + def move_cursor_up(self): self.roster_win.move_cursor_up() + return True + + def on_scroll_down(self): + # Scroll info win + pass + + def on_scroll_up(self): + # Scroll info down + pass def on_info_win_size_changed(self, _, __): pass diff --git a/src/user.py b/src/user.py index 1ed2a9f1..b2b8790c 100644 --- a/src/user.py +++ b/src/user.py @@ -31,7 +31,6 @@ class User(object): keep trace of an user in a Room """ def __init__(self, nick, affiliation, show, status, role): - from common import debug self.last_talked = datetime(1, 1, 1) # The oldest possible time self.update(affiliation, show, status, role) self.change_nick(nick) diff --git a/src/window.py b/src/window.py index 0e628a8c..eb2c157b 100644 --- a/src/window.py +++ b/src/window.py @@ -518,8 +518,11 @@ class TextWin(Win): class Input(Win): """ The line where text is entered + It can be in input mode or in commmand mode. + Command mode means that single_key_commands can be entered, handled + by the Tab object, while this input just displays an help text. """ - def __init__(self, height, width, y, x, stdscr, visible): + def __init__(self, height, width, y, x, stdscr, visible, input_mode=True, help_text=''): self.key_func = { "KEY_LEFT": self.key_left, "M-D": self.key_left, @@ -548,6 +551,8 @@ class Input(Win): } Win.__init__(self, height, width, y, x, stdscr) + self.input_mode = input_mode + self.help_text = help_text # the text displayed in command_mode self.visible = visible self.history = [] self.text = '' @@ -875,8 +880,12 @@ class Input(Win): """ with g_lock: self.clear_text() - self.addstr(self.text[self.line_pos:self.line_pos+self.width-1]) - self.addstr(0, self.pos, '') # WTF, this works but .move() doesn't... + if self.input_mode: + self.addstr(self.text[self.line_pos:self.line_pos+self.width-1]) + else: + self.addstr(self.help_text, curses.color_pair(theme.COLOR_INFORMATION_BAR)) + self.finish_line(theme.COLOR_INFORMATION_BAR) + self.addstr(0, self.pos, '') # WTF, this works but .move() doesn't… self._refresh() def refresh(self): @@ -928,7 +937,7 @@ class RosterWin(Win): 'chat':theme.COLOR_STATUS_CHAT, 'unavailable':theme.COLOR_STATUS_UNAVAILABLE } - # subscription_char = {'both': ' + def __init__(self, height, width, y, x, parent_win, visible): self.visible = visible Win.__init__(self, height, width, y, x, parent_win) @@ -967,10 +976,14 @@ class RosterWin(Win): return with g_lock: self.roster_len = len(roster) + while self.roster_len and self.pos >= self.roster_len: + self.move_cursor_up() self._win.erase() self.draw_roster_information(roster) y = 1 for group in roster.get_groups(): + if group.get_nb_connected_contacts() == 0: + continue # Ignore empty groups # This loop is really REALLY ugly :^) if y-1 == self.pos: self.selected_row = group @@ -980,6 +993,10 @@ class RosterWin(Win): if group.folded: continue for contact in group.get_contacts(): + if config.get('roster_show_offline', 'false') == 'false' and\ + contact.get_nb_resources() == 0: + continue + if y-1 == self.pos: self.selected_row = contact if y-self.start_pos+1 == self.height: @@ -1007,7 +1024,7 @@ class RosterWin(Win): def draw_plus(self, y): """ Draw the indicator that shows that - the list is longer that what is displayed + the list is longer than what is displayed """ self.addstr(y, self.width-5, '++++', curses.color_pair(42)) -- cgit v1.2.3