summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/common.py8
-rw-r--r--src/contact.py30
-rw-r--r--src/gui.py123
-rw-r--r--src/keyboard.py2
-rw-r--r--src/roster.py150
-rw-r--r--src/tab.py98
-rw-r--r--src/theme.py3
-rw-r--r--src/user.py1
-rw-r--r--src/window.py214
9 files changed, 511 insertions, 118 deletions
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 '<Roster_group: %s; %s>' % (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
+