Basic implementation of the roster and one to one conversations
This commit is contained in:
parent
c486b78bbd
commit
390e952829
9 changed files with 512 additions and 119 deletions
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
|
123
src/gui.py
123
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)
|
||||
|
|
|
@ -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
|
||||
|
|
152
src/roster.py
152
src/roster.py
|
@ -16,30 +16,146 @@
|
|||
|
||||
from contact import Contact
|
||||
|
||||
from common import debug
|
||||
|
||||
class Roster(object):
|
||||
"""
|
||||
Defines the roster
|
||||
"""
|
||||
def __init__(self):
|
||||
self._contacts = {}
|
||||
self._contacts = {} # key = jid; value = Contact()
|
||||
self._roster_groups = []
|
||||
|
||||
def addContactToList(self, contact):
|
||||
assert isinstance(contact, Contact)
|
||||
assert contact not in self._contacts
|
||||
self._contacts[contact.getJid().bare] = contact
|
||||
def add_contact(self, contact, jid):
|
||||
"""
|
||||
Add a contact to the contact list
|
||||
"""
|
||||
assert jid not in self._contacts
|
||||
self._contacts[jid] = contact
|
||||
|
||||
def getContacts(self):
|
||||
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):
|
||||
"""
|
||||
returns all the contacts in a list
|
||||
TODO: sorted
|
||||
TODO: only some contacts (online only for example)
|
||||
Edit the groups the contact is in
|
||||
Add or remove RosterGroup if needed
|
||||
"""
|
||||
return [contact for contact in self._contacts.keys()]
|
||||
# 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 len(self._contacts)
|
||||
"""
|
||||
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 getContact(self, bare_jid):
|
||||
if bare_jid not in self._contacts:
|
||||
return None
|
||||
return self._contacts[bare_jid]
|
||||
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):
|
||||
"""
|
||||
A RosterGroup is a group containing contacts
|
||||
It can be Friends/Family etc, but also can be
|
||||
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
|
||||
|
||||
def remove_contact(self, contact):
|
||||
"""
|
||||
Remove a Contact object to the list
|
||||
"""
|
||||
assert isinstance(contact, Contact)
|
||||
assert contact in self._contacts
|
||||
self._contacts.remove(contact)
|
||||
|
||||
def add_contact(self, contact):
|
||||
"""
|
||||
append a Contact object to the list
|
||||
"""
|
||||
assert isinstance(contact, Contact)
|
||||
assert contact not in self._contacts
|
||||
self._contacts.append(contact)
|
||||
|
||||
def get_contacts(self):
|
||||
return self._contacts
|
||||
|
||||
def __repr__(self):
|
||||
return '<Roster_group: %s; %s>' % (self.name, self._contacts)
|
||||
|
|
98
src/tab.py
98
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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
214
src/window.py
214
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
|
||||
|
||||
|
|
Loading…
Reference in a new issue