From 390e952829dfbf9bed2cf4647e07ab9249d59cf2 Mon Sep 17 00:00:00 2001 From: "louiz@4325f9fc-e183-4c21-96ce-0ab188b42d13" Date: Sun, 26 Sep 2010 18:01:38 +0000 Subject: Basic implementation of the roster and one to one conversations --- src/common.py | 8 +-- src/contact.py | 30 +++++++- src/gui.py | 123 +++++++++++++++++++++++++++++--- src/keyboard.py | 2 +- src/roster.py | 150 ++++++++++++++++++++++++++++++++++----- src/tab.py | 98 ++++++++++++++++++++++---- src/theme.py | 3 +- src/user.py | 1 - src/window.py | 214 +++++++++++++++++++++++++++++++++++++++----------------- 9 files changed, 511 insertions(+), 118 deletions(-) (limited to 'src') diff --git a/src/common.py b/src/common.py index 061c9d84..cbe29fb0 100644 --- a/src/common.py +++ b/src/common.py @@ -58,13 +58,7 @@ def debug(string): a CLI software """ fdes = open("/tmp/debug", 'a') - try: - fdes.write(string) - except: - try: - fdes.write(string.encode('utf-8')) - except: - fdes.write(string.encode('utf-8')) + fdes.write(string) fdes.close() def get_base64_from_file(path): diff --git a/src/contact.py b/src/contact.py index c3b07fc2..510e208a 100644 --- a/src/contact.py +++ b/src/contact.py @@ -22,8 +22,32 @@ class Contact(object): """ def __init__(self, jid): self._jid = JID(jid) # a SleekXMPP jid object - self._display_name = None - self.groups = [] # a list of groups the contact is in + self._display_name = '' + self._subscription = 'none' + self._ask = None + self._status = '' + self._presence = 'unavailable' + self._priority = 0 + self._groups = [] # a list of groups the contact is in - def getJid(self): + def set_ask(self, ask): + self._ask = ask + + def set_subscription(self, sub): + self._subscription = sub + + def get_jid(self): return self._jid + + def __repr__(self): + return '%s' % self._jid + + def set_priority(self, priority): + assert isinstance(priority, int) + self._priority = priority + + def set_presence(self, pres): + self._presence = pres + + def get_presence(self): + return self._presence diff --git a/src/gui.py b/src/gui.py index 2f4023a0..30624331 100644 --- a/src/gui.py +++ b/src/gui.py @@ -35,15 +35,17 @@ import theme import multiuserchat as muc from handler import Handler from config import config -from tab import MucTab, InfoTab, PrivateTab, RosterInfoTab +from tab import MucTab, InfoTab, PrivateTab, RosterInfoTab, ConversationTab from user import User from room import Room from roster import Roster +from contact import Contact from message import Message from text_buffer import TextBuffer from keyboard import read_char from common import is_jid_the_same, jid_get_domain, jid_get_resource, is_jid +from common import debug # http://xmpp.org/extensions/xep-0045.html#errorstatus ERROR_AND_STATUS_CODES = { '401': _('A password is required'), @@ -75,8 +77,9 @@ class Gui(object): self.init_curses(self.stdscr) self.xmpp = xmpp default_tab = InfoTab(self.stdscr, "Info") if self.xmpp.anon\ - else RosterInfoTab(self.stdscr, self.xmpp.roster) + else RosterInfoTab(self.stdscr) self.tabs = [default_tab] + self.roster = Roster() # a unique buffer used to store global informations # that are displayed in almost all tabs, in an # information window. @@ -136,8 +139,10 @@ class Gui(object): self.xmpp.add_event_handler("groupchat_presence", self.on_groupchat_presence) self.xmpp.add_event_handler("groupchat_message", self.on_groupchat_message) self.xmpp.add_event_handler("message", self.on_message) - self.xmpp.add_event_handler("presence", self.on_presence) + 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) def grow_information_win(self): """ @@ -159,6 +164,25 @@ class Gui(object): tab.on_info_win_size_changed(self.information_win_size, self.stdscr) self.refresh_window() + def on_got_offline(self, presence): + jid = presence['from'] + contact = self.roster.get_contact_by_jid(jid.bare) + if not contact: + return + contact.set_presence('unavailable') + self.information('%s is not offline' % (contact.get_jid()), "Roster") + + def on_got_online(self, presence): + jid = presence['from'] + contact = self.roster.get_contact_by_jid(jid.bare) + if not contact: + return + status = presence['type'] + priority = presence.getPriority() + contact.set_presence(status) + contact.set_priority(priority) + self.information("%s is now online (%s)" % (contact.get_jid(), status), "Roster") + def on_connected(self, event): """ Called when we are connected and authenticated @@ -409,6 +433,15 @@ class Gui(object): """ from common import debug debug('MESSAGE: %s\n' % (message)) + jid = message['from'].bare + room = self.get_conversation_by_jid(jid) + if not room: + room = self.open_conversation_window(jid, False) + if not room: + return + body = message['body'] + self.add_message_to_text_buffer(room, body, None, jid) + self.refresh_window() return def on_presence(self, presence): @@ -423,10 +456,24 @@ class Gui(object): A subscription changed, or we received a roster item after a roster request, etc """ - from common import debug - debug("ROSTER UPDATE: %s\n" % (iq)) - for subscriber in iq['roster']['items']: - debug("subscriber: %s\n" % (iq['roster']['items'][subscriber]['subscription'])) + # debug('Roster Update: \n%s\n\n' % iq) + for item in iq.findall('{jabber:iq:roster}query/{jabber:iq:roster}item'): + jid = item.attrib['jid'] + contact = self.roster.get_contact_by_jid(jid) + if not contact: + contact = Contact(jid) + self.roster.add_contact(contact, jid) + if 'ask' in item.attrib: + contact.set_ask(item.attrib['ask']) + else: + contact.set_ask(None) + if item.attrib['subscription']: + contact.set_subscription(item.attrib['subscription']) + 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 resize_window(self): """ @@ -456,6 +503,16 @@ class Gui(object): """ return self.tabs[0] + def get_conversation_by_jid(self, jid): + """ + Return the room of the ConversationTab with the given jid + """ + for tab in self.tabs: + if isinstance(tab, ConversationTab): + if tab.get_name() == jid: + return tab.get_room() + return None + def get_room_by_name(self, name): """ returns the room that has this name @@ -489,7 +546,8 @@ class Gui(object): Refresh everything """ self.current_tab().set_color_state(theme.COLOR_TAB_CURRENT) - self.current_tab().refresh(self.tabs, self.information_buffer) + self.current_tab().refresh(self.tabs, self.information_buffer, self.roster) + doupdate() def open_new_room(self, room, nick, focus=True): """ @@ -584,6 +642,26 @@ class Gui(object): self.add_message_to_text_buffer(room, _('You can join the room with an other nick, by typing "/join /other_nick"')) self.refresh_window() + def open_conversation_window(self, room_name, focus=True): + """ + open a new conversation tab and focus it if needed + """ + r = Room(room_name, self.xmpp.fulljid) + new_tab = ConversationTab(self.stdscr, r, self.information_win_size) + # insert it in the rooms + if self.current_tab().nb == 0: + self.tabs.append(new_tab) + else: + for ta in self.tabs: + if ta.nb == 0: + self.tabs.insert(self.tabs.index(ta), new_tab) + break + if focus: # focus the room if needed + self.command_win('%s' % (new_tab.nb)) + # self.window.new_room(r) + self.refresh_window() + return r + def open_private_window(self, room_name, user_nick, focus=True): complete_jid = room_name+'/'+user_nick for tab in self.tabs: # if the room exists, focus it and return @@ -1178,8 +1256,31 @@ class Gui(object): if not key: return res = self.current_tab().on_input(key) - if key in ('^J', '\n'): + if not res: + return + if key in ('^J', '\n') and isinstance(res, str): self.execute(res) + else: + # we did "enter" with an empty input in the roster + self.on_roster_enter_key(res) + + def on_roster_enter_key(self, roster_row): + """ + when enter is pressed on the roster window + """ + if isinstance(roster_row, Contact): + r = Room(roster_row.get_jid().full, self.xmpp.fulljid) + new_tab = ConversationTab(self.stdscr, r, self.information_win_size) + debug('%s\n'% new_tab) + # insert it in the tabs + if self.current_tab().nb == 0: + self.tabs.append(new_tab) + else: + for ta in self.tabs: + if ta.nb == 0: + self.tabs.insert(self.tabs.index(ta), new_tab) + break + self.refresh_window() def execute(self,line): """ @@ -1203,6 +1304,10 @@ class Gui(object): def command_say(self, line): if isinstance(self.current_tab(), PrivateTab): muc.send_private_message(self.xmpp, self.current_tab().get_name(), line) + elif isinstance(self.current_tab(), ConversationTab): # todo, special case + muc.send_private_message(self.xmpp, self.current_tab().get_name(), line) + if isinstance(self.current_tab(), PrivateTab) or\ + isinstance(self.current_tab(), ConversationTab): self.add_message_to_text_buffer(self.current_tab().get_room(), line, None, self.current_tab().get_room().own_nick) elif isinstance(self.current_tab(), MucTab): muc.send_groupchat_message(self.xmpp, self.current_tab().get_name(), line) diff --git a/src/keyboard.py b/src/keyboard.py index a0ac8d6d..4a577d16 100644 --- a/src/keyboard.py +++ b/src/keyboard.py @@ -52,7 +52,7 @@ def read_char(s): if first == 27: (first, c) = get_next_byte(s) if not isinstance(first, int): - return Nones + return None return "M-"+chr(first) if 194 <= first: (code, c) = get_next_byte(s) # 2 bytes char diff --git a/src/roster.py b/src/roster.py index 421e05e4..e6be9208 100644 --- a/src/roster.py +++ b/src/roster.py @@ -16,30 +16,146 @@ from contact import Contact +from common import debug + class Roster(object): + def __init__(self): + self._contacts = {} # key = jid; value = Contact() + self._roster_groups = [] + + def add_contact(self, contact, jid): + """ + Add a contact to the contact list + """ + assert jid not in self._contacts + self._contacts[jid] = contact + + def get_contact_len(self): + return len(self._contacts.keys()) + + def get_contact_by_jid(self, jid): + if jid in self._contacts: + return self._contacts[jid] + return None + + def edit_groups_of_contact(self, contact, groups): + """ + Edit the groups the contact is in + Add or remove RosterGroup if needed + """ + # add the contact to each group he is in + for group in groups: + if group in contact._groups: + continue + else: + # create the group if it doesn't exist yet + contact._groups.append(group) + 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: + # the contact is not in the group anymore + self.remove_contact_from_group(group, contact) + + def remove_contact_from_group(self, group_name, contact): + """ + Remove the contact from the group. + Remove also the group if this makes it empty + """ + for group in self._roster_groups: + if group.name == group_name: + group.remove_contact(contact) + if group.is_empty(): + self._roster_groups.remove(group) + return + + def add_contact_to_group(self, group_name, contact): + """ + Add the contact to the group. + Create the group if it doesn't already exist + """ + for group in self._roster_groups: + if group.name == group_name: + group.add_contact(contact) + return + new_group = RosterGroup(group_name) + self._roster_groups.append(new_group) + new_group.add_contact(contact) + + # def ordered_by_group(self, dic_roster, order): + # # ordered by contact + # for jid in dic_roster: + # if not dic_roster[jid]['in_roster']: + # continue + # self.contact_number += 1 + # groups=dic_roster[jid]['groups'] + # if not groups: + # groups = ['(none)'] + # new_contact = Contact(jid, name=dic_roster[jid]['name'], + # groups=groups, + # subscription=dic_roster[jid]['subscription'], + # presence=dic_roster[jid]['presence']) + # for group in groups: + # self.add_contact_to_group(group, new_contact) + # # debug('Jid:%s, (%s)\n' % (jid, dic_roster[jid])) + # debug('\n') + + def get_groups(self): + return self._roster_groups + + def __len__(self): + """ + Return the number of line that would be printed + """ + l = 0 + for group in self._roster_groups: + l += 1 + if not group.folded: + for contact in group.get_contacts(): + l += 1 + return l + + def __repr__(self): + ret = '== Roster:\nContacts:\n' + for contact in self._contacts: + ret += '%s\n' % (contact,) + ret += 'Groups\n' + for group in self._roster_groups: + ret += '%s\n' % (group,) + return ret + '\n' + +class RosterGroup(object): """ - Defines the roster + A RosterGroup is a group containing contacts + It can be Friends/Family etc, but also can be + Online/Offline or whatever """ - def __init__(self): - self._contacts = {} + 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 - def addContactToList(self, contact): + def remove_contact(self, contact): + """ + Remove a Contact object to the list + """ assert isinstance(contact, Contact) - assert contact not in self._contacts - self._contacts[contact.getJid().bare] = contact + assert contact in self._contacts + self._contacts.remove(contact) - def getContacts(self): + def add_contact(self, contact): """ - returns all the contacts in a list - TODO: sorted - TODO: only some contacts (online only for example) + append a Contact object to the list """ - return [contact for contact in self._contacts.keys()] + assert isinstance(contact, Contact) + assert contact not in self._contacts + self._contacts.append(contact) - def __len__(self): - return len(self._contacts) + def get_contacts(self): + return self._contacts - def getContact(self, bare_jid): - if bare_jid not in self._contacts: - return None - return self._contacts[bare_jid] + def __repr__(self): + return '' % (self.name, self._contacts) diff --git a/src/tab.py b/src/tab.py index 64c3c788..51707561 100644 --- a/src/tab.py +++ b/src/tab.py @@ -44,7 +44,7 @@ class Tab(object): else: self.visible = True - def refresh(self, tabs, informations): + def refresh(self, tabs, informations, roster): """ Called on each screen refresh (when something has changed) """ @@ -136,7 +136,7 @@ class InfoTab(Tab): self.text_win.resize(self.height-2, self.width, 0, 0, stdscr, self.visible) self.input.resize(1, self.width, self.height-1, 0, stdscr, self.visible) - def refresh(self, tabs, informations): + def refresh(self, tabs, informations, _): self.text_win.refresh(informations) self.tab_win.refresh(tabs, tabs[0]) self.input.refresh() @@ -206,7 +206,7 @@ class MucTab(Tab): self.tab_win.resize(1, self.width, self.height-2, 0, stdscr, self.visible) self.input.resize(1, self.width, self.height-1, 0, stdscr, self.visible) - def refresh(self, tabs, informations): + def refresh(self, tabs, informations, _): self.topic_win.refresh(self._room.topic) self.text_win.refresh(self._room) self.v_separator.refresh() @@ -311,7 +311,7 @@ class PrivateTab(Tab): self.tab_win.resize(1, self.width, self.height-2, 0, stdscr, self.visible) self.input.resize(1, self.width, self.height-1, 0, stdscr, self.visible) - def refresh(self, tabs, informations): + def refresh(self, tabs, informations, _): self.text_win.refresh(self._room) self.info_header.refresh(self._room) self.info_win.refresh(informations) @@ -358,12 +358,11 @@ class PrivateTab(Tab): class RosterInfoTab(Tab): """ - A tab, splitted in two, containg the roster and infos + A tab, splitted in two, containing the roster and infos """ - def __init__(self, stdscr, roster): + def __init__(self, stdscr): Tab.__init__(self, stdscr) self.name = "Roster" - self.roster = roster roster_width = self.width//2 info_width = self.width-roster_width-1 self.v_separator = window.VerticalSeparator(self.height-2, 1, 0, roster_width, stdscr, self.visible) @@ -383,8 +382,8 @@ class RosterInfoTab(Tab): self.roster_win.resize(self.height-2, roster_width, 0, 0, stdscr, self.visible) self.input.resize(1, self.width, self.height-1, 0, stdscr, self.visible) - def refresh(self, tabs, informations): - self.roster_win.refresh(self.roster) + def refresh(self, tabs, informations, roster): + self.roster_win.refresh(roster) self.v_separator.refresh() self.info_win.refresh(informations) self.tab_win.refresh(tabs, tabs[0]) @@ -400,6 +399,8 @@ 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() return self.input.do_command(key) def on_lose_focus(self): @@ -412,10 +413,81 @@ class RosterInfoTab(Tab): return False def on_scroll_down(self): - debug('TODO DOWN') + self.roster_win.move_cursor_down() def on_scroll_up(self): - debug('TODO UP') + self.roster_win.move_cursor_up() - def on_info_win_size_changed(self): - return + def on_info_win_size_changed(self, _, __): + pass + + def on_enter(self): + debug('%s\n' % (self.roster_win.get_selected_row())) + return self.roster_win.get_selected_row() + +class ConversationTab(Tab): + """ + The tab containg a normal conversation (someone from our roster) + """ + def __init__(self, stdscr, room, info_win_size): + Tab.__init__(self, stdscr) + self.info_win_size = info_win_size + self._room = room + self.text_win = window.TextWin(self.height-2-self.info_win_size, self.width, 0, 0, stdscr, self.visible) + self.info_header = window.ConversationInfoWin(1, self.width, self.height-3-self.info_win_size, 0, stdscr, self.visible) + self.info_win = window.TextWin(self.info_win_size, self.width, self.height-2-self.info_win_size, 0, stdscr, self.visible) + self.tab_win = window.GlobalInfoBar(1, self.width, self.height-2, 0, stdscr, self.visible) + self.input = window.Input(1, self.width, self.height-1, 0, stdscr, self.visible) + + def resize(self, stdscr): + Tab.resize(self, stdscr) + self.text_win.resize(self.height-2-self.info_win_size, self.width, 0, 0, stdscr, self.visible) + self.info_header.resize(1, self.width, self.height-3-self.info_win_size, 0, stdscr, self.visible) + self.info_win.resize(self.info_win_size, self.width, self.height-2-self.info_win_size, 0, stdscr, self.visible) + self.tab_win.resize(1, self.width, self.height-2, 0, stdscr, self.visible) + self.input.resize(1, self.width, self.height-1, 0, stdscr, self.visible) + + def refresh(self, tabs, informations, _): + self.text_win.refresh(self._room) + self.info_header.refresh(self._room) + self.info_win.refresh(informations) + self.tab_win.refresh(tabs, tabs[0]) + self.input.refresh() + + def get_color_state(self): + if self._room.color_state == theme.COLOR_TAB_NORMAL or\ + self._room.color_state == theme.COLOR_TAB_CURRENT: + return self._room.color_state + return theme.COLOR_TAB_PRIVATE + + def set_color_state(self, color): + self._room.color_state = color + + def get_name(self): + return self._room.name + + def on_input(self, key): + return self.input.do_command(key) + + def on_lose_focus(self): + self._room.set_color_state(theme.COLOR_TAB_NORMAL) + self._room.remove_line_separator() + self._room.add_line_separator() + + def on_gain_focus(self): + self._room.set_color_state(theme.COLOR_TAB_CURRENT) + + def on_scroll_up(self): + self._room.scroll_up(self.text_win.height-1) + + def on_scroll_down(self): + self._room.scroll_down(self.text_win.height-1) + + def on_info_win_size_changed(self, size, stdscr): + self.info_win_size = size + self.text_win.resize(self.height-2, self.width, 0, 0, stdscr, self.visible) + self.info_header.resize(1, self.width, self.height-3-self.info_win_size, 0, stdscr, self.visible) + self.info_win.resize(self.info_win_size, (self.width//10)*9, self.height-2-self.info_win_size, 0, stdscr, self.visible) + + def get_room(self): + return self._room diff --git a/src/theme.py b/src/theme.py index dda01166..7285ceb0 100644 --- a/src/theme.py +++ b/src/theme.py @@ -74,7 +74,8 @@ COLOR_STATUS_NONE = 0 COLOR_STATUS_DND = 21 COLOR_STATUS_AWAY = 35 COLOR_STATUS_CHAT = 28 - +COLOR_STATUS_UNAVAILABLE = 57 +COLOR_STATUS_ONLINE = 41 # Bars COLOR_INFORMATION_BAR = 42 COLOR_TOPIC_BAR = 42 diff --git a/src/user.py b/src/user.py index 252764ae..8ddf47fc 100644 --- a/src/user.py +++ b/src/user.py @@ -32,7 +32,6 @@ class User(object): """ def __init__(self, nick, affiliation, show, status, role): from common import debug - debug('NEW USER: nick:%s, affiliation:%s, show:%s, status:%s, role:%s\n' % (nick, affiliation, show, status, role)) 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 162d10f7..6e0ec174 100644 --- a/src/window.py +++ b/src/window.py @@ -28,15 +28,21 @@ from config import config from threading import Lock from message import Line +from tab import MIN_WIDTH, MIN_HEIGHT + import theme +from common import debug + g_lock = Lock() class Win(object): def __init__(self, height, width, y, x, parent_win): - self._resize(height, width, y, x, parent_win) + self._resize(height, width, y, x, parent_win, True) - def _resize(self, height, width, y, x, parent_win): + def _resize(self, height, width, y, x, parent_win, visible): + if not visible: + return self.height, self.width, self.x, self.y = height, width, x, y try: self.win = curses.newwin(height, width, y, x) @@ -44,6 +50,8 @@ class Win(object): from common import debug debug('%s %s %s %s %s\n' % (height, width, y, x, parent_win)) raise + import os + os.abort() # When resizing in a too little height (less than 3 lines) # We don't need to resize the window, since this size # just makes no sense @@ -72,6 +80,14 @@ class Win(object): """ self.win.addstr(*args) + def finish_line(self, color): + """ + Write colored spaces until the end of line + """ + (y, x) = self.win.getyx() + size = self.width-x + self.addnstr(' '*size, size, curses.color_pair(color)) + class UserList(Win): def __init__(self, height, width, y, x, parent_win, visible): Win.__init__(self, height, width, y, x, parent_win) @@ -117,7 +133,7 @@ class UserList(Win): self.visible = visible if not visible: return - self._resize(height, width, y, x, stdscr) + self._resize(height, width, y, x, stdscr, visible) self.win.attron(curses.color_pair(theme.COLOR_VERTICAL_SEPARATOR)) self.win.vline(0, 0, curses.ACS_VLINE, self.height) self.win.attroff(curses.color_pair(theme.COLOR_VERTICAL_SEPARATOR)) @@ -128,7 +144,7 @@ class Topic(Win): Win.__init__(self, height, width, y, x, parent_win) def resize(self, height, width, y, x, stdscr, visible): - self._resize(height, width, y, x, stdscr) + self._resize(height, width, y, x, stdscr, visible) def refresh(self, topic): if not self.visible: @@ -150,7 +166,7 @@ class GlobalInfoBar(Win): Win.__init__(self, height, width, y, x, parent_win) def resize(self, height, width, y, x, stdscr, visible): - self._resize(height, width, y, x, stdscr) + self._resize(height, width, y, x, stdscr, visible) def refresh(self, tabs, current): if not self.visible: @@ -199,14 +215,6 @@ class InfoWin(Win): plus = ' -PLUS(%s)-' % text_buffer.pos self.addnstr(plus, len(plus), curses.color_pair(theme.COLOR_SCROLLABLE_NUMBER) | curses.A_BOLD) - def finish_line(self): - """ - Write colored spaces until the end of line - """ - (y, x) = self.win.getyx() - size = self.width-x - self.addnstr(' '*size, size, curses.color_pair(theme.COLOR_INFORMATION_BAR)) - class PrivateInfoWin(InfoWin): """ The live above the information window, displaying informations @@ -216,7 +224,7 @@ class PrivateInfoWin(InfoWin): InfoWin.__init__(self, height, width, y, x, parent_win, visible) def resize(self, height, width, y, x, stdscr, visible): - self._resize(height, width, y, x, stdscr) + self._resize(height, width, y, x, stdscr, visible) def refresh(self, room): if not self.visible: @@ -225,7 +233,7 @@ class PrivateInfoWin(InfoWin): self.win.erase() self.write_room_name(room) self.print_scroll_position(room) - self.finish_line() + self.finish_line(theme.COLOR_INFORMATION_BAR) self.win.refresh() g_lock.release() @@ -235,6 +243,34 @@ class PrivateInfoWin(InfoWin): txt = ' from room %s' % room_name self.addnstr(txt, len(txt), curses.color_pair(theme.COLOR_INFORMATION_BAR)) +class ConversationInfoWin(InfoWin): + """ + The line above the information window, displaying informations + about the MUC user we are talking to + """ + def __init__(self, height, width, y, x, parent_win, visible): + InfoWin.__init__(self, height, width, y, x, parent_win, visible) + + def resize(self, height, width, y, x, stdscr, visible): + self._resize(height, width, y, x, stdscr, visible) + + def refresh(self, room): + if not self.visible: + return + g_lock.acquire() + self.win.erase() + self.write_room_name(room) + self.print_scroll_position(room) + self.finish_line(theme.COLOR_INFORMATION_BAR) + self.win.refresh() + g_lock.release() + + def write_room_name(self, room): + # (room_name, nick) = room.name.split('/', 1) + # self.addnstr(nick, len(nick), curses.color_pair(13)) + txt = '%s' % room.name + self.addnstr(txt, len(txt), curses.color_pair(theme.COLOR_INFORMATION_BAR)) + class MucInfoWin(InfoWin): """ The line just above the information window, displaying informations @@ -244,7 +280,7 @@ class MucInfoWin(InfoWin): InfoWin.__init__(self, height, width, y, x, parent_win, visible) def resize(self, height, width, y, x, stdscr, visible): - self._resize(height, width, y, x, stdscr) + self._resize(height, width, y, x, stdscr, visible) def refresh(self, room): if not self.visible: @@ -256,7 +292,7 @@ class MucInfoWin(InfoWin): self.write_disconnected(room) self.write_role(room) self.print_scroll_position(room) - self.finish_line() + self.finish_line(theme.COLOR_INFORMATION_BAR) self.win.refresh() g_lock.release() @@ -291,8 +327,6 @@ class MucInfoWin(InfoWin): """ Write our own role and affiliation """ - from common import debug - own_user = None for user in room.users: if user.nick == room.own_nick: @@ -430,7 +464,6 @@ class TextWin(Win): self.win.attroff(curses.color_pair(color)) else: # Special messages like join or quit - from common import debug special_words = { theme.CHAR_JOIN: theme.COLOR_JOIN_CHAR, theme.CHAR_QUIT: theme.COLOR_QUIT_CHAR, @@ -507,7 +540,7 @@ class TextWin(Win): def resize(self, height, width, y, x, stdscr, visible): self.visible = visible - self._resize(height, width, y, x, stdscr) + self._resize(height, width, y, x, stdscr, visible) class Input(Win): """ @@ -554,11 +587,14 @@ class Input(Win): self.last_completion = None # Contains the last nickname completed, # if last key was a tab + def is_empty(self): + return len(self.text) == 0 + def resize(self, height, width, y, x, stdscr, visible): self.visible = visible if not visible: return - self._resize(height, width, y, x, stdscr) + self._resize(height, width, y, x, stdscr, visible) self.win.clear() self.addnstr(0, 0, self.text, self.width-1) @@ -896,7 +932,7 @@ class VerticalSeparator(Win): def resize(self, height, width, y, x, stdscr, visible): self.visible = visible - self._resize(height, width, y, x, stdscr) + self._resize(height, width, y, x, stdscr, visible) if not visible: return @@ -906,70 +942,116 @@ class VerticalSeparator(Win): self.rewrite_line() class RosterWin(Win): + color_show = {'xa':theme.COLOR_STATUS_XA, + 'none':theme.COLOR_STATUS_ONLINE, + '':theme.COLOR_STATUS_ONLINE, + 'available':theme.COLOR_STATUS_ONLINE, + 'dnd':theme.COLOR_STATUS_DND, + 'away':theme.COLOR_STATUS_AWAY, + 'chat':theme.COLOR_STATUS_CHAT, + 'unavailable': theme.COLOR_STATUS_UNAVAILABLE + } + def __init__(self, height, width, y, x, parent_win, visible): self.visible = visible Win.__init__(self, height, width, y, x, parent_win) - self.pos = 0 # position in the contact list + self.pos = 0 # cursor position in the contact list + self.start_pos = 1 # position of the start of the display + self.roster_len = 0 + self.selected_row = None def resize(self, height, width, y, x, stdscr, visible): - self._resize(height, width, y, x, stdscr) + self._resize(height, width, y, x, stdscr, visible) self.visible = visible + def move_cursor_down(self): + if self.pos < self.roster_len-1: + self.pos += 1 + if self.pos == self.start_pos-1 + self.height-1: + self.scroll_down() + + def move_cursor_up(self): + if self.pos > 0: + self.pos -= 1 + if self.pos == self.start_pos-2: + self.scroll_up() + + def scroll_down(self): + self.start_pos += 8 + + def scroll_up(self): + self.start_pos -= 8 + def refresh(self, roster): """ We get the roster object """ - from common import debug - debug('anus%s, %s' % (roster, self.visible)) if not self.visible: return g_lock.acquire() + # debug('Len roster: %s, pos: %s, startpos: %s\n(%s:%s)' % (len(roster), self.pos, self.start_pos, self.width, self.height)) + self.roster_len = len(roster) self.win.erase() - # TODO, two ways of scrolling - # currently: always centered - if self.pos > self.height//2 and\ - self.pos + self.height//2 < len(roster.getContacts()): - # We are centered - begin = True - end = True - pos = self.height//2 - contacts = roster.getContacts()[self.pos-pos:self.pos+pos+1] - elif self.pos <= self.height//2: - # we are at the beginning of the list - pos = self.pos - contacts = roster.getContacts()[:self.height] - begin = False - if self.height < len(roster.getContacts()): - end = True - else: - end = False - else: - # we are at the end of the list - pos = self.height - (len(roster.getContacts()) - self.pos) - contacts = roster.getContacts()[-self.height:] - begin = True - end = False - cpt = 0 # ipair ou chais plus quoi - for contact in contacts: - if cpt == pos: - self.draw_contact_line(contact, cpt, 0, 3) - else: - self.draw_contact_line(contact, cpt, 0) - cpt += 1 - if end: - self.win.addstr(self.height-1, 0, '++++') - if begin: - self.win.addstr(0, 0, '++++') + self.draw_roster_information(roster) + y = 1 + for group in roster.get_groups(): + if y-1 == self.pos: + self.selected_row = group + if y >= self.start_pos: + self.draw_group(y-self.start_pos+1, group, y-1==self.pos) + y += 1 + for contact in group.get_contacts(): + if y-1 == self.pos: + self.selected_row = contact + if y-self.start_pos+1 == self.height: + break + if y >= self.start_pos: + self.draw_contact_line(y-self.start_pos+1, contact, y-1==self.pos) + y += 1 + if y-self.start_pos+1 == self.height: + break + if self.start_pos > 1: + self.draw_plus(1) + if self.start_pos + self.height-2 < self.roster_len: + self.draw_plus(self.height-1) self.win.refresh() g_lock.release() - def draw_contact_line(self, contact, x, y, color=None): + def draw_plus(self, y): + """ + Draw the indicator that shows that + the list is longer that what is displayed + """ + self.win.addstr(y, self.width-4, '+++', curses.color_pair(42)) + + def draw_roster_information(self, roster): + """ + """ + self.win.addstr('%s contacts' % roster.get_contact_len(), curses.color_pair(12)) + self.finish_line(12) + + def draw_group(self, y, group, colored): + """ + Draw a groupname on a line + """ + if colored: + self.addstr(y, 0, group.name, curses.color_pair(34)) + else: + self.addstr(y, 0, group.name) + + def draw_contact_line(self, y, contact, colored): """ Draw on a line all informations about one contact Use 'color' to draw the jid/display_name to show what is is currently selected contact in the list """ - if color: - self.win.addstr(x, y, contact.getJid().full, curses.color_pair(color)) + color = RosterWin.color_show[contact.get_presence()] + self.win.addstr(y, 1, "!", curses.color_pair(color)) + if colored: + self.win.addstr(y, 2, contact.get_jid().bare, curses.color_pair(34)) else: - self.win.addstr(x, y, contact.getJid().full) + self.win.addstr(y, 2, contact.get_jid().bare) + + def get_selected_row(self): + return self.selected_row + -- cgit v1.2.3