diff options
-rw-r--r-- | src/contact.py | 127 | ||||
-rw-r--r-- | src/gui.py | 60 | ||||
-rw-r--r-- | src/roster.py | 13 | ||||
-rw-r--r-- | src/tab.py | 16 | ||||
-rw-r--r-- | src/window.py | 81 |
5 files changed, 230 insertions, 67 deletions
diff --git a/src/contact.py b/src/contact.py index 8966289b..3a0ea30e 100644 --- a/src/contact.py +++ b/src/contact.py @@ -16,31 +16,16 @@ from sleekxmpp.xmlstream.jid import JID -class Contact(object): +class Resource(object): """ - Defines a roster item + Defines a roster item. + It's a precise resource. """ def __init__(self, jid): - self._jid = JID(jid) # a SleekXMPP jid object - self._display_name = None - self._subscription = 'none' - self._ask = None + self._jid = JID(jid) # Full jid self._status = '' self._presence = 'unavailable' self._priority = 0 - self._groups = [] # a list of groups the contact is in - - def set_ask(self, ask): - self._ask = ask - - def get_ask(self): - return self._ask - - def set_subscription(self, sub): - self._subscription = sub - - def get_subscription(self, sub): - return self._subscription def get_jid(self): return self._jid @@ -52,6 +37,9 @@ class Contact(object): assert isinstance(priority, int) self._priority = priority + def get_priority(self): + return self._priority + def set_presence(self, pres): self._presence = pres @@ -64,8 +52,109 @@ class Contact(object): def set__status(self, s): self._status = s +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 + to get the resource with the highest priority, etc + """ + def __init__(self, bare_jid): + self._jid = bare_jid + self._resources = [] + self._folded = True # Folded by default + self._display_name = None + self._subscription = 'none' + self._ask = None + self._groups = [] # a list of groups the contact is in + + def get_bare_jid(self): + """ + Just get the bare_jid or the contact + """ + return self._jid + + def get_highest_priority_resource(self): + """ + There must be, at any time, at least ONE resource. + And they always should be ordered by priority. + """ + ret = None + for resource in self._resources: + if not ret or ret.get_priority() < resource.get_priority(): + ret = resource + return ret + + def add_resource(self, resource): + """ + Called, for example, when a new resource get offline + (the first, or any subsequent one) + """ + # TODO sort by priority + self._resources.append(resource) + + def remove_resource(self, resource): + """ + Called, for example, when one resource goes offline. + """ + self._resources.remove(resource) + + def remove_resource_by_fulljid(self, fulljid): + """ + Like 'remove_resource' but just by knowing the full jid + """ + for resource in self._resources: + if resource.get_jid().full == fulljid: + self._resources.remove(resource) + return + assert False + + def get_resource_by_fulljid(self, fulljid): + """ + Return the resource with the given fulljid + """ + for resource in self._resources: + if resource.get_jid().full == fulljid: + return resource + return None + def toggle_folded(self): + """ + Fold if it's unfolded, and vice versa + """ + self._folded = not self._folded + def set_name(self, name): self._display_name = name def get_name(self): return self._display_name + + def set_ask(self, ask): + self._ask = ask + + def get_ask(self): + return self._ask + + def set_subscription(self, sub): + self._subscription = sub + + def get_subscription(self, sub): + return self._subscription + + def get_nb_resources(self): + """ + Get the number of connected resources + """ + return len(self._resources) + + def get_resources(self): + """ + Return all resources + """ + compare_resources = lambda x: x.get_priority() + return sorted(self._resources, key=compare_resources) + + def __repr__(self): + ret = '<Contact: %s' % self._jid + for resource in self._resources: + ret += '\n\t\t%s'%resource + return ret + ' />\n' @@ -40,13 +40,12 @@ from tab import MucTab, InfoTab, PrivateTab, RosterInfoTab, ConversationTab from user import User from room import Room from roster import Roster, RosterGroup -from contact import Contact +from contact import Contact, Resource from message import Message from text_buffer import TextBuffer from keyboard import read_char from common import jid_get_domain, is_jid -from common import debug # http://xmpp.org/extensions/xep-0045.html#errorstatus ERROR_AND_STATUS_CODES = { '401': _('A password is required'), @@ -148,7 +147,7 @@ class Gui(object): self.xmpp.add_event_handler("got_online" , self.on_got_online) self.xmpp.add_event_handler("got_offline" , self.on_got_offline) self.xmpp.add_event_handler("roster_update", self.on_roster_update) - # self.xmpp.add_event_handler("presence", self.on_presence) + self.xmpp.add_event_handler("changed_status", self.on_presence) def grow_information_win(self): """ @@ -175,19 +174,28 @@ class Gui(object): contact = self.roster.get_contact_by_jid(jid.bare) if not contact: return - contact.set_presence('unavailable') - self.information('%s is offline' % (contact.get_jid()), "Roster") + resource = contact.get_resource_by_fulljid(jid.full) + assert resource + self.information('%s is offline' % (resource.get_jid()), "Roster") + contact.remove_resource(resource) + if isinstance(self.current_tab(), RosterInfoTab): + self.refresh_window() def on_got_online(self, presence): jid = presence['from'] contact = self.roster.get_contact_by_jid(jid.bare) if not contact: + # Todo, handle presence comming from contacts not in roster return + resource = contact.get_resource_by_fulljid(jid.full) + assert not resource + resource = Resource(jid.full) status = presence['type'] - priority = presence.getPriority() - contact.set_presence(status) - contact.set_priority(priority) - self.information("%s is online (%s)" % (contact.get_jid(), status), "Roster") + priority = presence.getPriority() or 0 + resource.set_presence(status) + resource.set_priority(priority) + contact.add_resource(resource) + self.information("%s is online (%s)" % (resource.get_jid().full, status), "Roster") def on_connected(self, event): """ @@ -445,8 +453,6 @@ class Gui(object): """ When receiving "normal" messages (from someone in our roster) """ - from common import debug - debug('MESSAGE: %s\n' % (message)) jid = message['from'].bare room = self.get_conversation_by_jid(jid) body = message['body'] @@ -463,9 +469,20 @@ class Gui(object): def on_presence(self, presence): """ """ - from common import debug - debug('Presence: %s\n' % (presence)) - return + jid = presence['from'] + # contact = ros + contact = self.roster.get_contact_by_jid(jid.bare) + if not contact: + return + resource = contact.get_resource_by_fulljid(jid.full) + if not resource: + return + status = presence['type'] + priority = presence.getPriority() or 0 + resource.set_presence(status) + resource.set_priority(priority) + if isinstance(self.current_tab(), RosterInfoTab): + self.refresh_window() def on_roster_update(self, iq): """ @@ -491,7 +508,6 @@ class Gui(object): groups = item.findall('{jabber:iq:roster}group') self.roster.edit_groups_of_contact(contact, [group.text for group in groups]) if isinstance(self.current_tab(), RosterInfoTab): - # TODO refresh roster_win only self.refresh_window() def call_for_resize(self): @@ -1297,12 +1313,16 @@ class Gui(object): when enter is pressed on the roster window """ if isinstance(roster_row, Contact): - if not self.get_conversation_by_jid(roster_row.get_jid().bare): - self.open_conversation_window(roster_row.get_jid().bare) + # roster_row.toggle_folded() + if not self.get_conversation_by_jid(roster_row.get_bare_jid()): + self.open_conversation_window(roster_row.get_bare_jid()) + else: + self.focus_tab_named(roster_row.get_bare_jid()) + if isinstance(roster_row, Resource): + if not self.get_conversation_by_jid(roster_row.get_jid().full): + self.open_conversation_window(roster_row.get_jid().full) else: - self.focus_tab_named(roster_row.get_jid().bare) - elif isinstance(roster_row, RosterGroup): - roster_row.folded = not roster_row.folded + self.focus_tab_named(roster_row.get_jid().full) self.refresh_window() def execute(self,line): diff --git a/src/roster.py b/src/roster.py index 72f885bc..03a5f93a 100644 --- a/src/roster.py +++ b/src/roster.py @@ -14,13 +14,11 @@ # You should have received a copy of the GNU General Public License # along with Poezio. If not, see <http://www.gnu.org/licenses/>. -from contact import Contact - -from common import debug +from contact import Contact, Resource class Roster(object): def __init__(self): - self._contacts = {} # key = jid; value = Contact() + self._contacts = {} # key = bare jid; value = Contact() self._roster_groups = [] def add_contact(self, contact, jid): @@ -97,6 +95,8 @@ class Roster(object): if not group.folded: for contact in group.get_contacts(): l += 1 + if not contact._folded: + l += contact.get_nb_resources() return l def __repr__(self): @@ -115,10 +115,10 @@ class RosterGroup(object): Online/Offline or whatever """ def __init__(self, name, folded=False): - # debug('New group: %s \n' % name) self._contacts = [] self.name = name self.folded = folded # if the group content is to be shown + def is_empty(self): return len(self._contacts) == 0 @@ -143,3 +143,6 @@ class RosterGroup(object): def __repr__(self): return '<Roster_group: %s; %s>' % (self.name, self._contacts) + + def toggle_folded(self): + self.folded = not self.folded @@ -29,8 +29,7 @@ import window import theme import curses from roster import RosterGroup - -from common import debug +from contact import Contact, Resource class Tab(object): """ @@ -426,7 +425,10 @@ class RosterInfoTab(Tab): def on_input(self, key): if key in ('\n', '^J', '^M') and self.input.is_empty(): return self.on_enter() - return self.input.do_command(key) + if key == ' ': + return self.on_space() + # In writting mode + # return self.input.do_command(key) def on_lose_focus(self): self._color_state = theme.COLOR_TAB_NORMAL @@ -447,6 +449,12 @@ class RosterInfoTab(Tab): def on_info_win_size_changed(self, _, __): pass + def on_space(self): + selected_row = self.roster_win.get_selected_row() + if isinstance(selected_row, RosterGroup) or\ + isinstance(selected_row, Contact): + selected_row.toggle_folded() + return True def on_enter(self): selected_row = self.roster_win.get_selected_row() return selected_row @@ -478,7 +486,7 @@ class ConversationTab(Tab): def refresh(self, tabs, informations, roster): self.text_win.refresh(self._room) - self.info_header.refresh(self._room, roster.get_contact_by_jid(self._room.name)) + # self.info_header.refresh(self._room, roster.get_contact_by_jid(self._room.name)) self.info_win.refresh(informations) self.tab_win.refresh(tabs, tabs[0]) self.input.refresh() diff --git a/src/window.py b/src/window.py index c8a05625..0e628a8c 100644 --- a/src/window.py +++ b/src/window.py @@ -27,7 +27,7 @@ from config import config from threading import Lock -from contact import Contact +from contact import Contact, Resource from roster import RosterGroup from message import Line @@ -35,8 +35,6 @@ from tab import MIN_WIDTH, MIN_HEIGHT import theme -from common import debug - g_lock = Lock() class Win(object): @@ -255,11 +253,13 @@ class ConversationInfoWin(InfoWin): # contact can be None, if we receive a message # from someone not in our roster. In this case, we display # only the maximum information from the message we can get. + # Also, contact can be a resource, if we're talking to a + # specific resource. with g_lock: self._win.erase() - self.write_room_name(contact, room) - self.print_scroll_position(room) - self.finish_line(theme.COLOR_INFORMATION_BAR) + # self.write_room_name(resource, room) + # self.print_scroll_position(room) + # self.finish_line(theme.COLOR_INFORMATION_BAR) self._refresh() def write_room_name(self, contact, room): @@ -926,7 +926,7 @@ class RosterWin(Win): 'dnd':theme.COLOR_STATUS_DND, 'away':theme.COLOR_STATUS_AWAY, 'chat':theme.COLOR_STATUS_CHAT, - 'unavailable': theme.COLOR_STATUS_UNAVAILABLE + 'unavailable':theme.COLOR_STATUS_UNAVAILABLE } # subscription_char = {'both': ' def __init__(self, height, width, y, x, parent_win, visible): @@ -971,6 +971,7 @@ class RosterWin(Win): self.draw_roster_information(roster) y = 1 for group in roster.get_groups(): + # This loop is really REALLY ugly :^) if y-1 == self.pos: self.selected_row = group if y >= self.start_pos: @@ -986,6 +987,15 @@ class RosterWin(Win): if y >= self.start_pos: self.draw_contact_line(y-self.start_pos+1, contact, y-1==self.pos) y += 1 + if not contact._folded: + for resource in contact.get_resources(): + if y-1 == self.pos: + self.selected_row = resource + if y-self.start_pos+1 == self.height: + break + if y >= self.start_pos: + self.draw_resource_line(y-self.start_pos+1, resource, y-1==self.pos) + y += 1 if y-self.start_pos+1 == self.height: break if self.start_pos > 1: @@ -1023,21 +1033,46 @@ class RosterWin(Win): def draw_contact_line(self, y, contact, colored): """ - Draw on a line all informations about one contact + Draw on a line all informations about one contact. + This is basically the highest priority resource's informations Use 'color' to draw the jid/display_name to show what is - is currently selected contact in the list - """ - color = RosterWin.color_show[contact.get_presence()] + the currently selected contact in the list + """ + resource = contact.get_highest_priority_resource() + if not resource: + # There's no online resource + presence = 'unavailable' + folder = ' ' + nb = '' + else: + presence = resource.get_presence() + folder = '[+]' if contact._folded else '[-]' + nb = '(%s)' % (contact.get_nb_resources(),) + color = RosterWin.color_show[presence] if contact.get_name(): - display_name = '%s (%s)' % (contact.get_name(), - contact.get_jid().bare) + display_name = '%s (%s) %s' % (contact.get_name(), + contact.get_bare_jid(), nb,) else: - display_name = '%s' % (contact.get_jid().bare,) + display_name = '%s %s' % (contact.get_bare_jid(), nb,) self.addstr(y, 1, " ", curses.color_pair(color)) + if resource: + self.addstr(y, 2, ' [+]' if contact._folded else ' [-]') + self.addstr(' ') if colored: - self.addstr(y, 4, display_name, curses.color_pair(14)) + self.addstr(display_name, curses.color_pair(14)) else: - self.addstr(y, 4, display_name) + self.addstr(display_name) + + def draw_resource_line(self, y, resource, colored): + """ + Draw a specific resource line + """ + color = RosterWin.color_show[resource.get_presence()] + self.addstr(y, 4, " ", curses.color_pair(color)) + if colored: + self.addstr(y, 6, resource.get_jid().full, curses.color_pair(14)) + else: + self.addstr(y, 6, resource.get_jid().full) def get_selected_row(self): return self.selected_row @@ -1051,12 +1086,17 @@ class ContactInfoWin(Win): self._resize(height, width, y, x, stdscr, visible) self.visible = visible - def draw_contact_info(self, contact): + def draw_contact_info(self, resource, jid=None): """ draw the contact information """ - self.addstr(0, 0, contact.get_jid().full, curses.color_pair(theme.COLOR_INFORMATION_BAR)) - self.addstr(' (%s)'%(contact.get_presence(),), curses.color_pair(theme.COLOR_INFORMATION_BAR)) + jid = jid or resource.get_jid().full + if resource: + presence = resource.get_presence() + else: + presence = 'unavailable' + self.addstr(0, 0, jid, curses.color_pair(theme.COLOR_INFORMATION_BAR)) + self.addstr(' (%s)'%(presence,), curses.color_pair(theme.COLOR_INFORMATION_BAR)) self.finish_line(theme.COLOR_INFORMATION_BAR) def draw_group_info(self, group): @@ -1074,5 +1114,8 @@ class ContactInfoWin(Win): if isinstance(selected_row, RosterGroup): self.draw_group_info(selected_row) elif isinstance(selected_row, Contact): + self.draw_contact_info(selected_row.get_highest_priority_resource(), + selected_row.get_bare_jid()) + elif isinstance(selected_row, Resource): self.draw_contact_info(selected_row) self._refresh() |