summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorlouiz@4325f9fc-e183-4c21-96ce-0ab188b42d13 <louiz@4325f9fc-e183-4c21-96ce-0ab188b42d13>2010-09-14 02:11:07 +0000
committerlouiz@4325f9fc-e183-4c21-96ce-0ab188b42d13 <louiz@4325f9fc-e183-4c21-96ce-0ab188b42d13>2010-09-14 02:11:07 +0000
commit33f197b4ab8f65e33c30b789fb34157b70f1105d (patch)
tree161def6a9dc7f943c2677bbe68b86c631b2f10bc
parent29e5250904bacbabb49388fee0811aa4cdcb6708 (diff)
downloadpoezio-33f197b4ab8f65e33c30b789fb34157b70f1105d.tar.gz
poezio-33f197b4ab8f65e33c30b789fb34157b70f1105d.tar.bz2
poezio-33f197b4ab8f65e33c30b789fb34157b70f1105d.tar.xz
poezio-33f197b4ab8f65e33c30b789fb34157b70f1105d.zip
complete refactoring of the ui. Everything is now very modulable. Little info win added at the bottom. Roster is ready to be implemented.
-rw-r--r--src/gui.py612
-rw-r--r--src/poezio.py4
-rw-r--r--src/room.py74
-rw-r--r--src/tab.py357
-rw-r--r--src/text_buffer.py62
-rw-r--r--src/theme.py1
-rw-r--r--src/window.py329
7 files changed, 967 insertions, 472 deletions
diff --git a/src/gui.py b/src/gui.py
index 0a56caba..06627048 100644
--- a/src/gui.py
+++ b/src/gui.py
@@ -35,23 +35,25 @@ import theme
import multiuserchat as muc
from handler import Handler
from config import config
+from tab import MucTab, InfoTab, PrivateTab
from window import Window
from user import User
from room import Room
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
# http://xmpp.org/extensions/xep-0045.html#errorstatus
ERROR_AND_STATUS_CODES = {
- '401': 'A password is required',
- '403': 'You are banned from the room',
- '404': 'The room does\'nt exist',
- '405': 'Your are not allowed to create a new room',
- '406': 'A reserved nick must be used',
- '407': 'You are not in the member list',
- '409': 'This nickname is already in use or has been reserved',
- '503': 'The maximum number of users has been reached',
+ '401': _('A password is required'),
+ '403': _('You are banned from the room'),
+ '404': _('The room does\'nt exist'),
+ '405': _('Your are not allowed to create a new room'),
+ '406': _('A reserved nick must be used'),
+ '407': _('You are not in the member list'),
+ '409': _('This nickname is already in use or has been reserved'),
+ '503': _('The maximum number of users has been reached'),
}
SHOW_NAME = {
@@ -72,8 +74,12 @@ class Gui(object):
self.stdscr = curses.initscr()
self.init_curses(self.stdscr)
self.xmpp = xmpp
- self.window = Window(self.stdscr)
- self.rooms = [Room('Info', '', self.window)]
+ self.tabs = [InfoTab(self.stdscr, "lol info")]
+ # a unique buffer used to store global informations
+ # that are displayed in almost all tabs, in an
+ # information window.
+ self.information_buffer = TextBuffer()
+ self.information_win_size = 2 # Todo, get this from config
self.ignores = {}
self.commands = {
@@ -110,41 +116,16 @@ class Gui(object):
}
self.key_func = {
- "KEY_LEFT": self.window.input.key_left,
- "M-D": self.window.input.key_left,
- "KEY_RIGHT": self.window.input.key_right,
- "M-C": self.window.input.key_right,
- "KEY_UP": self.window.input.key_up,
- "M-A": self.window.input.key_up,
- "KEY_END": self.window.input.key_end,
- "KEY_HOME": self.window.input.key_home,
- "KEY_DOWN": self.window.input.key_down,
- "M-B": self.window.input.key_down,
"KEY_PPAGE": self.scroll_page_up,
"KEY_NPAGE": self.scroll_page_down,
- "KEY_DC": self.window.input.key_dc,
"KEY_F(5)": self.rotate_rooms_left,
"^P": self.rotate_rooms_left,
"KEY_F(6)": self.rotate_rooms_right,
+ "KEY_F(7)": self.shrink_information_win,
+ "KEY_F(8)": self.grow_information_win,
"^N": self.rotate_rooms_right,
- "\t": self.completion,
- "^I": self.completion,
- "KEY_BTAB": self.last_words_completion,
"KEY_RESIZE": self.resize_window,
- "KEY_BACKSPACE": self.window.input.key_backspace,
- '^?': self.window.input.key_backspace,
- '^J': self.execute,
- '\n': self.execute,
- '^D': self.window.input.key_dc,
- '^W': self.window.input.delete_word,
- '^K': self.window.input.delete_end_of_line,
- '^U': self.window.input.delete_begining_of_line,
- '^Y': self.window.input.paste_clipboard,
- '^A': self.window.input.key_home,
- '^E': self.window.input.key_end,
- 'M-f': self.window.input.jump_word_right,
'^X': self.go_to_important_room,
- 'M-b': self.window.input.jump_word_left,
'^V': self.move_separator,
}
@@ -155,14 +136,26 @@ class Gui(object):
self.xmpp.add_event_handler("message", self.on_message)
self.xmpp.add_event_handler("presence", self.on_presence)
self.xmpp.add_event_handler("roster_update", self.on_roster_update)
- # self.handler = Handler()
- # self.handler.connect('on-connected', self.on_connected)
- # self.handler.connect('join-room', self.join_room)
- # self.handler.connect('room-presence', self.room_presence)
- # self.handler.connect('room-message', self.room_message)
- # self.handler.connect('private-message', self.private_message)
- # self.handler.connect('error-message', self.room_error)
- # self.handler.connect('error', self.information)
+
+ def grow_information_win(self):
+ """
+ """
+ if self.information_win_size == 14:
+ return
+ self.information_win_size += 1
+ for tab in self.tabs:
+ tab.on_info_win_size_changed(self.information_win_size, self.stdscr)
+ self.refresh_window()
+
+ def shrink_information_win(self):
+ """
+ """
+ if self.information_win_size == 0:
+ return
+ self.information_win_size -= 1
+ for tab in self.tabs:
+ tab.on_info_win_size_changed(self.information_win_size, self.stdscr)
+ self.refresh_window()
def on_connected(self, event):
"""
@@ -231,9 +224,9 @@ class Gui(object):
if from_nick == room.own_nick:
room.joined = True
new_user.color = theme.COLOR_OWN_NICK
- self.add_message_to_room(room, _("Your nickname is %s") % (from_nick))
+ self.add_message_to_text_buffer(room, _("Your nickname is %s") % (from_nick))
if '170' in status_codes:
- self.add_message_to_room(room, 'Warning: this room is publicly logged')
+ self.add_message_to_text_buffer(room, 'Warning: this room is publicly logged')
else:
change_nick = '303' in status_codes
kick = '307' in status_codes and typ == 'unavailable'
@@ -253,9 +246,7 @@ class Gui(object):
# status change
else:
self.on_user_change_status(room, user, from_nick, from_room, affiliation, role, show, status)
- if room == self.current_room():
- self.window.user_win.refresh(room.users)
- self.window.input.refresh()
+ self.refresh_window()
doupdate()
def on_user_join(self, room, from_nick, affiliation, show, status, role, jid):
@@ -267,26 +258,26 @@ class Gui(object):
hide_exit_join = config.get('hide_exit_join', -1)
if hide_exit_join != 0:
if not jid.full:
- self.add_message_to_room(room, _('%(spec)s "[%(nick)s]" joined the room') % {'nick':from_nick.replace('"', '\\"'), 'spec':theme.CHAR_JOIN.replace('"', '\\"')}, colorized=True)
+ self.add_message_to_text_buffer(room, _('%(spec)s "[%(nick)s]" joined the room') % {'nick':from_nick.replace('"', '\\"'), 'spec':theme.CHAR_JOIN.replace('"', '\\"')}, colorized=True)
else:
- self.add_message_to_room(room, _('%(spec)s "[%(nick)s]" "(%(jid)s)" joined the room') % {'spec':theme.CHAR_JOIN.replace('"', '\\"'), 'nick':from_nick.replace('"', '\\"'), 'jid':jid.full}, colorized=True)
+ self.add_message_to_text_buffer(room, _('%(spec)s "[%(nick)s]" "(%(jid)s)" joined the room') % {'spec':theme.CHAR_JOIN.replace('"', '\\"'), 'nick':from_nick.replace('"', '\\"'), 'jid':jid.full}, colorized=True)
def on_user_nick_change(self, room, presence, user, from_nick, from_room):
new_nick = presence.find('{http://jabber.org/protocol/muc#user}x/{http://jabber.org/protocol/muc#user}item').attrib['nick']
if user.nick == room.own_nick:
room.own_nick = new_nick
# also change our nick in all private discussion of this room
- for _room in self.rooms:
- if _room.jid is not None and is_jid_the_same(_room.jid, room.name):
- _room.own_nick = new_nick
+ for _tab in self.tabs:
+ if isinstance(_tab, PrivateTab) and is_jid_the_same(_tab.get_jid(), room.name):
+ _tab.own_nick = new_nick
user.change_nick(new_nick)
- self.add_message_to_room(room, _('"[%(old)s]" is now known as "[%(new)s]"') % {'old':from_nick.replace('"', '\\"'), 'new':new_nick.replace('"', '\\"')}, colorized=True)
+ self.add_message_to_text_buffer(room, _('"[%(old)s]" is now known as "[%(new)s]"') % {'old':from_nick.replace('"', '\\"'), 'new':new_nick.replace('"', '\\"')}, colorized=True)
# rename the private tabs if needed
private_room = self.get_room_by_name('%s/%s' % (from_room, from_nick))
if private_room:
- self.add_message_to_room(private_room, _('"[%(old_nick)s]" is now known as "[%(new_nick)s]"') % {'old_nick':from_nick.replace('"', '\\"'), 'new_nick':new_nick.replace('"', '\\"')}, colorized=True)
+ self.add_message_to_text_buffer(private_room, _('"[%(old_nick)s]" is now known as "[%(new_nick)s]"') % {'old_nick':from_nick.replace('"', '\\"'), 'new_nick':new_nick.replace('"', '\\"')}, colorized=True)
new_jid = private_room.name.split('/')[0]+'/'+new_nick
- private_room.jid = private_room.name = new_jid
+ private_room.name = new_jid
def on_user_kicked(self, room, presence, user, from_nick):
"""
@@ -302,7 +293,7 @@ class Gui(object):
if from_nick == room.own_nick: # we are kicked
room.disconnect()
if by:
- kick_msg = _('%(spec) [You] have been kicked by "[%(by)s]"') % {'spec': theme.CHAR_KICK.replace('"', '\\"'), 'by':by}
+ kick_msg = _('%(spec)s [You] have been kicked by "[%(by)s]"') % {'spec': theme.CHAR_KICK.replace('"', '\\"'), 'by':by}
else:
kick_msg = _('%(spec)s [You] have been kicked.') % {'spec':theme.CHAR_KICK.replace('"', '\\"')}
# try to auto-rejoin
@@ -315,7 +306,7 @@ class Gui(object):
kick_msg = _('%(spec)s "[%(nick)s]" has been kicked') % {'spec':theme.CHAR_KICK, 'nick':from_nick.replace('"', '\\"')}
if reason:
kick_msg += _(' Reason: %(reason)s') % {'reason': reason}
- self.add_message_to_room(room, kick_msg, colorized=True)
+ self.add_message_to_text_buffer(room, kick_msg, colorized=True)
def on_user_leave_groupchat(self, room, user, jid, status, from_nick, from_room):
"""
@@ -327,32 +318,32 @@ class Gui(object):
if not jid.full:
leave_msg = _('%(spec)s "[%(nick)s]" has left the room') % {'nick':from_nick.replace('"', '\\"'), 'spec':theme.CHAR_QUIT.replace('"', '\\"')}
else:
- leave_msg = _('%(spec)s "[%(nick)s]" (%(jid)s) has left the room') % {'spec':theme.CHAR_QUIT.replace('"', '\\"'), 'nick':from_nick.replace('"', '\\"'), 'jid':jid.full}
+ leave_msg = _('%(spec)s "[%(nick)s]" (%(jid)s) has left the room') % {'spec':theme.CHAR_QUIT.replace('"', '\\"'), 'nick':from_nick.replace('"', '\\"'), 'jid':jid.full.replace('"', '\\"')}
if status:
leave_msg += ' (%s)' % status
- self.add_message_to_room(room, leave_msg, colorized=True)
+ self.add_message_to_text_buffer(room, leave_msg, colorized=True)
private_room = self.get_room_by_name('%s/%s' % (from_room, from_nick))
if private_room:
if not status:
- self.add_message_to_room(private_room, _('%(spec)s "[%(nick)s]" has left the room') % {'nick':from_nick.replace('"', '\\"'), 'spec':theme.CHAR_QUIT.replace('"', '\\"')}, colorized=True)
+ self.add_message_to_text_buffer(private_room, _('%(spec)s "[%(nick)s]" has left the room') % {'nick':from_nick.replace('"', '\\"'), 'spec':theme.CHAR_QUIT.replace('"', '\\"')}, colorized=True)
else:
- self.add_message_to_room(private_room, _('%(spec)s "[%(nick)s]" has left the room "(%(status)s)"') % {'nick':from_nick.replace('"', '\\"'), 'spec':theme.CHAR_QUIT, 'status': status.replace('"', '\\"')}, colorized=True)
+ self.add_message_to_text_buffer(private_room, _('%(spec)s "[%(nick)s]" has left the room "(%(status)s)"') % {'nick':from_nick.replace('"', '\\"'), 'spec':theme.CHAR_QUIT, 'status': status.replace('"', '\\"')}, colorized=True)
def on_user_change_status(self, room, user, from_nick, from_room, affiliation, role, show, status):
"""
When an user changes her status
"""
# build the message
- msg = _('%s changed his/her status: ')% from_nick
+ msg = _('"%s" changed: ')% from_nick.replace('"', '\\"')
if affiliation != user.affiliation:
- msg += _('affiliation: %s,') % affiliation
+ msg += _('affiliation: %s, ') % affiliation
if role != user.role:
- msg += _('role: %s,') % role
+ msg += _('role: %s, ') % role
if show != user.show and show in list(SHOW_NAME.keys()):
- msg += _('show: %s,') % SHOW_NAME[show]
+ msg += _('show: %s, ') % SHOW_NAME[show]
if status != user.status:
- msg += _('status: %s,') % status
- msg = msg[:-1] # remove the last ","
+ msg += _('status: %s, ') % status
+ msg = msg[:-2] # remove the last ", "
hide_status_change = config.get('hide_status_change', -1) if config.get('hide_status_change', -1) >= -1 else -1
if (hide_status_change == -1 or \
user.has_talked_since(hide_status_change) or\
@@ -363,10 +354,10 @@ class Gui(object):
show != user.show or\
status != user.status):
# display the message in the room
- self.add_message_to_room(room, msg)
+ self.add_message_to_text_buffer(room, msg, colorized=True)
private_room = self.get_room_by_name('%s/%s' % (from_room, from_nick))
if private_room: # display the message in private
- self.add_message_to_room(private_room, msg)
+ self.add_message_to_text_buffer(private_room, msg)
# finally, effectively change the user status
user.update(affiliation, show, status, role)
@@ -375,16 +366,14 @@ class Gui(object):
When receiving private message from a muc OR a normal message
(from one of our contacts)
"""
- from common import debug
-# debug('message: %s\n' % message)
if message['type'] == 'groupchat':
return None
# Differentiate both type of messages, and call the appropriate handler.
jid_from = message['from']
- for room in self.rooms:
- if room.jid is None and room.name == jid_from.bare: # check all the MUC we are in
+ for tab in self.tabs:
+ if isinstance(tab, MucTab) and tab.get_name() == jid_from.bare: # check all the MUC we are in
if message['type'] == 'error':
- return self.room_error(message, room.name)
+ return self.room_error(message, tab.get_room().name)
else:
return self.on_groupchat_private_message(message)
return self.on_normal_message(message)
@@ -393,6 +382,8 @@ class Gui(object):
"""
We received a Private Message (from someone in a Muc)
"""
+ from common import debug
+ debug('PRIVATE: %s\n' % message)
jid = message['from']
nick_from = jid.resource
room_from = jid.bare
@@ -402,8 +393,8 @@ class Gui(object):
if not room:
return
body = message['body']
- self.add_message_to_room(room, body, None, nick_from)
- self.window.input.refresh()
+ self.add_message_to_text_buffer(room, body, None, nick_from)
+ self.refresh_window()
doupdate()
def on_normal_message(self, message):
@@ -411,15 +402,14 @@ class Gui(object):
When receiving "normal" messages (from someone in our roster)
"""
from common import debug
-# debug('MESSAGE: %s\n' % (presence))
-
+ debug('MESSAGE: %s\n' % (message))
return
def on_presence(self, presence):
"""
"""
from common import debug
-# debug('PRESEEEEEEEEENCE: %s\n' % (presence))
+ debug('Presence: %s\n' % (presence))
return
def on_roster_update(self, iq):
@@ -428,7 +418,7 @@ class Gui(object):
after a roster request, etc
"""
from common import debug
-# debug("UPDATE: %s\n" % (iq))
+ debug("ROSTER UPDATE: %s\n" % (iq))
for subscriber in iq['roster']['items']:
debug("subscriber: %s\n" % (iq['roster']['items'][subscriber]['subscription']))
@@ -436,8 +426,9 @@ class Gui(object):
"""
Resize the whole screen
"""
- self.window.resize(self.stdscr)
- self.window.refresh(self.rooms)
+ for tab in self.tabs:
+ tab.resize(self.stdscr)
+ self.refresh_window()
def main_loop(self):
"""
@@ -451,23 +442,22 @@ class Gui(object):
if char in list(self.key_func.keys()):
self.key_func[char]()
else:
- if not char or len(char) > 1:
- continue # ignore non-handled keyboard shortcuts
- self.window.do_command(char)
+ self.do_command(char)
- def current_room(self):
+ def current_tab(self):
"""
returns the current room, the one we are viewing
"""
- return self.rooms[0]
+ return self.tabs[0]
def get_room_by_name(self, name):
"""
returns the room that has this name
"""
- for room in self.rooms:
- if room.name == name:
- return room
+ for tab in self.tabs:
+ if (isinstance(tab, MucTab) or
+ isinstance(tab, PrivateTab)) and tab.get_name() == name:
+ return tab.get_room()
return None
def init_curses(self, stdscr):
@@ -484,7 +474,6 @@ class Gui(object):
Reset terminal capabilities to what they were before ncurses
init
"""
- # TODO remove me?
curses.echo()
curses.nocbreak()
curses.endwin()
@@ -493,50 +482,26 @@ class Gui(object):
"""
Refresh everything
"""
- self.current_room().set_color_state(theme.COLOR_TAB_CURRENT)
- self.window.refresh(self.rooms)
+ self.current_tab().set_color_state(theme.COLOR_TAB_CURRENT)
+ self.current_tab().refresh(self.tabs, self.information_buffer)
def open_new_room(self, room, nick, focus=True):
"""
- Open a new Tab containing a Muc room, using the specified nick
+ Open a new MucTab containing a muc Room, using the specified nick
"""
- r = Room(room, nick, self.window)
- self.current_room().set_color_state(theme.COLOR_TAB_NORMAL)
- if self.current_room().nb == 0:
- self.rooms.append(r)
+ r = Room(room, nick)
+ new_tab = MucTab(self.stdscr, r, self.information_win_size)
+ if self.current_tab().nb == 0:
+ self.tabs.append(new_tab)
else:
- for ro in self.rooms:
- if ro.nb == 0:
- self.rooms.insert(self.rooms.index(ro), r)
+ for ta in self.tabs:
+ if ta.nb == 0:
+ self.tabs.insert(self.tabs.index(ta), new_tab)
break
if focus:
- self.command_win("%s" % r.nb)
+ self.command_win("%s" % new_tab.nb)
self.refresh_window()
- def completion(self):
- """
- Called when Tab is pressed, complete the nickname in the input
- """
- compare_users = lambda x: x.last_talked
- self.window.input.auto_completion([user.nick for user in sorted(self.current_room().users, key=compare_users, reverse=True)])
-
- def last_words_completion(self):
- """
- Complete the input with words recently said
- """
- # build the list of the recent words
- char_we_dont_want = [',', '(', ')', '.']
- words = list()
- for msg in self.current_room().messages[:-40:-1]:
- if not msg:
- continue
- for word in msg.txt.split():
- for char in char_we_dont_want: # remove the chars we don't want
- word = word.replace(char, '')
- if len(word) > 5:
- words.append(word)
- self.window.input.auto_completion(words, False)
-
def go_to_important_room(self):
"""
Go to the next room with activity, in this order:
@@ -544,56 +509,50 @@ class Gui(object):
- A Muc with an highlight
- A Muc with any new message
"""
- for room in self.rooms:
- if room.color_state == theme.COLOR_TAB_PRIVATE:
- self.command_win('%s' % room.nb)
+ for tab in self.tabs:
+ if tab.get_color_state() == theme.COLOR_TAB_PRIVATE:
+ self.command_win('%s' % tab.nb)
return
- for room in self.rooms:
- if room.color_state == theme.COLOR_TAB_HIGHLIGHT:
- self.command_win('%s' % room.nb)
+ for tab in self.tabs:
+ if tab.get_color_state() == theme.COLOR_TAB_HIGHLIGHT:
+ self.command_win('%s' % tab.nb)
return
- for room in self.rooms:
- if room.color_state == theme.COLOR_TAB_NEW_MESSAGE:
- self.command_win('%s' % room.nb)
+ for tab in self.tabs:
+ if tab.get_color_state() == theme.COLOR_TAB_NEW_MESSAGE:
+ self.command_win('%s' % tab.nb)
return
def rotate_rooms_right(self, args=None):
"""
rotate the rooms list to the right
"""
- self.current_room().set_color_state(theme.COLOR_TAB_NORMAL)
- self.current_room().remove_line_separator()
- self.rooms.append(self.rooms.pop(0))
- self.current_room().set_color_state(theme.COLOR_TAB_CURRENT)
+ self.current_tab().on_lose_focus()
+ self.tabs.append(self.tabs.pop(0))
+ self.current_tab().on_gain_focus()
self.refresh_window()
def rotate_rooms_left(self, args=None):
"""
rotate the rooms list to the right
"""
- self.current_room().set_color_state(theme.COLOR_TAB_NORMAL)
- self.current_room().remove_line_separator()
- self.rooms.insert(0, self.rooms.pop())
- self.current_room().set_color_state(theme.COLOR_TAB_CURRENT)
+ self.current_tab().on_lose_focus()
+ self.tabs.insert(0, self.tabs.pop())
+ self.current_tab().on_gain_focus()
self.refresh_window()
def scroll_page_down(self, args=None):
- self.current_room().scroll_down(self.window.text_win.height-1)
+ self.current_tab().on_scroll_down()
self.refresh_window()
def scroll_page_up(self, args=None):
- self.current_room().scroll_up(self.window.text_win.height-1)
+ self.current_tab().on_scroll_up()
self.refresh_window()
def room_error(self, error, room_name):
"""
Display the error on the room window
"""
- from common import debug
-# debug('ERROR: %s\n' % error)
room = self.get_room_by_name(room_name)
- if not room:
- room = self.get_room_by_name('Info')
msg = error['error']['type']
condition = error['error']['condition']
code = error['error']['code']
@@ -604,43 +563,45 @@ class Gui(object):
else:
body = condition or _('Unknown error')
if code:
- self.add_message_to_room(room, _('Error: %(code)s - %(msg)s: %(body)s' %
- {'msg':msg, 'body':body, 'code':code}))
+ msg = _('Error: %(code)s - %(msg)s: %(body)s') % {'msg':msg, 'body':body, 'code':code}
+ self.add_message_to_text_buffer(room, msg)
else:
- self.add_message_to_room(room, _('Error: %(msg)s: %(body)s' %
- {'msg':msg, 'body':body}))
+ msg = _('Error: %(msg)s: %(body)s') % {'msg':msg, 'body':body}
+ self.add_message_to_text_buffer(room, msg)
if code == '401':
- self.add_message_to_room(room, _('To provide a password in order to join the room, type "/join / password" (replace "password" by the real password)'))
+ msg = _('To provide a password in order to join the room, type "/join / password" (replace "password" by the real password)')
+ self.add_message_to_text_buffer(room, msg)
if code == '409':
if config.get('alternative_nickname', '') != '':
self.command_join('%s/%s'% (room.name, room.own_nick+config.get('alternative_nickname', '')))
else:
- self.add_message_to_room(room, _('You can join the room with an other nick, by typing "/join /other_nick"'))
+ 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_private_window(self, room_name, user_nick, focus=True):
complete_jid = room_name+'/'+user_nick
- for room in self.rooms: # if the room exists, focus it and return
- if room.jid:
- if room.jid == complete_jid:
- self.command_win('%s' % room.nb)
+ for tab in self.tabs: # if the room exists, focus it and return
+ if isinstance(tab, PrivateTab):
+ if tab.get_name() == complete_jid:
+ self.command_win('%s' % tab.nb)
return
# create the new tab
room = self.get_room_by_name(room_name)
if not room:
return None
own_nick = room.own_nick
- r = Room(complete_jid, own_nick, self.window, complete_jid)
+ r = Room(complete_jid, own_nick) # PrivateRoom here
+ new_tab = PrivateTab(self.stdscr, r, self.information_win_size)
# insert it in the rooms
- if self.current_room().nb == 0:
- self.rooms.append(r)
+ if self.current_tab().nb == 0:
+ self.tabs.append(new_tab)
else:
- for ro in self.rooms:
- if ro.nb == 0:
- self.rooms.insert(self.rooms.index(ro), r)
+ 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' % (r.nb))
+ self.command_win('%s' % (new_tab.nb))
# self.window.new_room(r)
self.refresh_window()
return r
@@ -683,70 +644,35 @@ class Gui(object):
# debug('======== %s, %s, %s, %s====\n'% (nick_from, room_from, body, subject))
if subject:
if nick_from:
- self.add_message_to_room(room, _("%(nick)s changed the subject to: %(subject)s") % {'nick':nick_from, 'subject':subject}, time=date)
+ self.add_message_to_text_buffer(room, _("%(nick)s changed the subject to: %(subject)s") % {'nick':nick_from, 'subject':subject}, time=date)
else:
- self.add_message_to_room(room, _("The subject is: %(subject)s") % {'subject':subject}, time=date)
+ self.add_message_to_text_buffer(room, _("The subject is: %(subject)s") % {'subject':subject}, time=date)
room.topic = subject.replace('\n', '|')
- if room == self.current_room():
- self.window.topic_win.refresh(room.topic)
elif body:
if body.startswith('/me '):
- self.add_message_to_room(room, "* "+nick_from + ' ' + body[4:], date)
+ self.add_message_to_text_buffer(room, "* "+nick_from + ' ' + body[4:], date)
else:
date = date if delayed == True else None
- self.add_message_to_room(room, body, date, nick_from)
+ self.add_message_to_text_buffer(room, body, date, nick_from)
self.refresh_window()
doupdate()
-
- def add_message_to_room(self, room, txt, time=None, nickname=None, colorized=False):
+ def add_message_to_text_buffer(self, room, txt, time=None, nickname=None, colorized=False):
"""
- Add the message to the room and refresh the associated component
- of the interface
+ Add the message to the room if possible, else, add it to the Info window
+ (in the Info tab of the info window in the RosterTab)
"""
- if room != self.current_room():
- room.add_line_separator()
- room.add_message(txt, time, nickname, colorized)
- if room == self.current_room():
- self.window.text_win.refresh(room)
+ if not room:
+ self.information('Error, trying to add a message in no room: %s' % txt)
else:
- self.window.info_win.refresh(self.rooms, self.current_room())
- self.window.input.refresh()
-
- def execute(self):
- """
- Execute the /command or just send the line on the current room
- """
- line = self.window.input.get_text()
- self.window.input.clear_text()
- self.window.input.refresh()
- if line == "":
- return
- if line.startswith('/') and not line.startswith('/me '):
- command = line.strip()[:].split()[0][1:]
- arg = line[2+len(command):] # jump the '/' and the ' '
- # example. on "/link 0 open", command = "link" and arg = "0 open"
- if command in list(self.commands.keys()):
- func = self.commands[command][0]
- func(arg)
- return
- else:
- self.add_message_to_room(self.current_room(), _("Error: unknown command (%s)") % (command))
- elif self.current_room().name != 'Info':
- if self.current_room().jid is not None:
- muc.send_private_message(self.xmpp, self.current_room().name, line)
- self.add_message_to_room(self.current_room(), line, None, self.current_room().own_nick)
- else:
- muc.send_groupchat_message(self.xmpp, self.current_room().name, line)
- self.window.input.refresh()
- doupdate()
+ room.add_message(txt, time, nickname, colorized)
+ self.refresh_window()
def command_help(self, arg):
"""
/help <command_name>
"""
args = arg.split()
- room = self.current_room()
if len(args) == 0:
msg = _('Available commands are: ')
for command in list(self.commands.keys()):
@@ -757,7 +683,7 @@ class Gui(object):
msg = self.commands[args[0]][1]
else:
msg = _('Unknown command: %s') % args[0]
- self.add_message_to_room(room, msg)
+ self.information(msg)
def command_whois(self, arg):
"""
@@ -769,10 +695,10 @@ class Gui(object):
try:
args = shlex.split(arg)
except ValueError as error:
- return self.add_message_to_room(self.current_room(), _("Error: %s") % (error))
+ return self.information(str(error), _("Error"))
room = self.current_room()
if len(args) != 1:
- self.add_message_to_room(room, _('whois command takes exactly one argument'))
+ self.add_message_to_text_buffer(room, _('whois command takes exactly one argument'))
return
# check if current room is a MUC
if room.jid or room.name == 'Info':
@@ -790,9 +716,10 @@ class Gui(object):
"""
Re-assign color to the participants of the room
"""
- room = self.current_room()
- if room.name == 'Info' or room.jid:
+ tab = self.current_tab()
+ if not isinstance(tab, MucTab):
return
+ room = tab.get_room()
i = 0
compare_users = lambda x: x.last_talked
users = list(room.users)
@@ -819,19 +746,18 @@ class Gui(object):
except ValueError:
self.command_help('win')
return
- if self.current_room().nb == nb:
+ if self.current_tab().nb == nb:
return
- self.current_room().set_color_state(theme.COLOR_TAB_NORMAL)
- self.current_room().remove_line_separator()
- start = self.current_room()
- self.rooms.append(self.rooms.pop(0))
- while self.current_room().nb != nb:
- self.rooms.append(self.rooms.pop(0))
- if self.current_room() == start:
- self.current_room().set_color_state(theme.COLOR_TAB_CURRENT)
+ self.current_tab().on_lose_focus()
+ start = self.current_tab()
+ self.tabs.append(self.tabs.pop(0))
+ while self.current_tab().nb != nb:
+ self.tabs.append(self.tabs.pop(0))
+ if self.current_tab() == start:
+ self.current_tab().set_color_state(theme.COLOR_TAB_CURRENT)
self.refresh_window()
return
- self.current_room().set_color_state(theme.COLOR_TAB_CURRENT)
+ self.current_tab().on_gain_focus()
self.refresh_window()
def command_kick(self, arg):
@@ -841,7 +767,9 @@ class Gui(object):
try:
args = shlex.split(arg)
except ValueError as error:
- return self.add_message_to_room(self.current_room(), _("Error: %s") % (error))
+ return self.information(str(error), _("Error"))
+ # TODO information message
+ # return self.add_message_to_text_buffer(self.current_room(), _("Error: %s") % (error))
if len(args) < 1:
self.command_help('kick')
return
@@ -850,26 +778,26 @@ class Gui(object):
reason = ' '.join(args[1:])
else:
reason = ''
- if self.current_room().name == 'Info' or not self.current_room().joined:
+ if not isinstance(self.current_tab(), MucTab) or not self.current_tab().get_room().joined:
return
- roomname = self.current_room().name
+ roomname = self.current_tab().get_name()
res = muc.eject_user(self.xmpp, roomname, nick, reason)
if res['type'] == 'error':
self.room_error(res, roomname)
- def command_say(self, arg):
- """
- /say <message>
- """
- line = arg
- if self.current_room().name != 'Info':
- if self.current_room().jid is not None:
- muc.send_private_message(self.xmpp, self.current_room().name, line)
- self.add_message_to_room(self.current_room(), line, None, self.current_room().own_nick)
- else:
- muc.send_groupchat_message(self.xmpp, self.current_room().name, line)
- self.window.input.refresh()
- doupdate()
+ # def command_say(self, arg):
+ # """
+ # /say <message>
+ # """
+ # line = arg
+ # if self.current_room().name != 'Info':
+ # if self.current_room().jid is not None:
+ # muc.send_private_message(self.xmpp, self.current_room().name, line)
+ # self.add_message_to_text_buffer(self.current_room(), line, None, self.current_room().own_nick)
+ # else:
+ # muc.send_groupchat_message(self.xmpp, self.current_room().name, line)
+ # self.window.input.refresh()
+ # doupdate()
def command_join(self, arg):
"""
@@ -878,11 +806,11 @@ class Gui(object):
args = arg.split()
password = None
if len(args) == 0:
- r = self.current_room()
- if r.name == 'Info':
+ t = self.current_tab()
+ if not isinstance(t, MucTab) and not isinstance(t, PrivateTab):
return
- room = r.name
- nick = r.own_nick
+ room = t.get_name()
+ nick = t.get_room().own_nick
else:
info = args[0].split('/')
if len(info) == 1:
@@ -893,32 +821,37 @@ class Gui(object):
else:
nick = info[1]
if info[0] == '': # happens with /join /nickname, which is OK
- r = self.current_room()
- if r.name == 'Info':
+ t = self.current_tab()
+ if not isinstance(t, MucTab):
return
- room = r.name
+ room = t.get_name()
if nick == '':
- nick = r.own_nick
+ nick = t.get_room().own_nick
else:
room = info[0]
if not is_jid(room): # no server is provided, like "/join hello"
# use the server of the current room if available
# check if the current room's name has a server
- if is_jid(self.current_room().name):
- room += '@%s' % jid_get_domain(self.current_room().name)
+ if isinstance(self.current_tab(), MucTab) and\
+ is_jid(self.current_tab().get_name()):
+ room += '@%s' % jid_get_domain(self.current_tab().get_name())
else: # no server could be found, print a message and return
- self.add_message_to_room(self.current_room(), _("You didn't specify a server for the room you want to join"))
+ # self.add_message_to_text_buffer(self.current_room(), _("You didn't specify a server for the room you want to join"))
+ # TODO INFO
return
- r = self.get_room_by_name(room)
+ r = self.get_room_by_name(room)
if len(args) == 2: # a password is provided
password = args[1]
- if r and r.joined: # if we are already in the room
- self.command_win('%s' % (r.nb))
- return
+ # TODO
+ # if r and r.joined: # if we are already in the room
+ # self.command_win('%s' % (r.nb))
+ # return
room = room.lower()
- self.xmpp.plugin['xep_0045'].joinMUC(room, nick, password)
+ if r and not r.joined:
+ self.xmpp.plugin['xep_0045'].joinMUC(room, nick, password)
if not r: # if the room window exists, we don't recreate it.
self.open_new_room(room, nick)
+ self.xmpp.plugin['xep_0045'].joinMUC(room, nick, password)
else:
r.own_nick = nick
r.users = []
@@ -929,11 +862,11 @@ class Gui(object):
"""
args = arg.split()
nick = None
+ if not isinstance(self.current_tab(), MucTab):
+ return
if len(args) == 0:
- room = self.current_room()
- if room.name == 'Info':
- return
- roomname = room.name
+ room = self.current_tab().get_room()
+ roomname = self.current_tab().get_name()
if room.joined:
nick = room.own_nick
else:
@@ -942,7 +875,7 @@ class Gui(object):
nick = info[1]
roomname = info[0]
if roomname == '':
- roomname = self.current_room().name
+ roomname = self.current_tab().get_name()
if nick:
res = roomname+'/'+nick
else:
@@ -958,7 +891,7 @@ class Gui(object):
bookmarked = ':'.join(bookmarked)
bookmarks = bookmarked+':'+res
config.set_and_save('rooms', bookmarks)
- self.add_message_to_room(self.current_room(), _('Your bookmarks are now: %s') % bookmarks)
+ self.information(_('Your bookmarks are now: %s') % bookmarks)
def command_set(self, arg):
"""
@@ -975,8 +908,7 @@ class Gui(object):
value = ''
config.set_and_save(option, value)
msg = "%s=%s" % (option, value)
- room = self.current_room()
- self.add_message_to_room(room, msg)
+ self.information(msg)
def command_show(self, arg):
"""
@@ -1004,9 +936,9 @@ class Gui(object):
msg = ' '.join(args[1:])
else:
msg = None
- for room in self.rooms:
- if room.joined:
- muc.change_show(self.xmpp, room.name, room.own_nick, show, msg)
+ for tab in self.tabs:
+ if isinstance(tab, MucTab) and tab.get_room().joined:
+ muc.change_show(self.xmpp, tab.get_room().name, tab.get_room().own_nick, show, msg)
def command_ignore(self, arg):
"""
@@ -1015,21 +947,21 @@ class Gui(object):
try:
args = shlex.split(arg)
except ValueError as error:
- return self.add_message_to_room(self.current_room(), _("Error: %s") % (error))
+ return self.information(str(error), _("Error"))
if len(args) != 1:
self.command_help('ignore')
return
- if self.current_room().name == 'Info' or not self.current_room().joined:
+ if not isinstance(self.current_tab(), MucTab):
return
- roomname = self.current_room().name
+ roomname = self.current_tab().get_name()
nick = args[0]
if roomname not in self.ignores:
self.ignores[roomname] = set() # no need for any order
if nick not in self.ignores[roomname]:
self.ignores[roomname].add(nick)
- self.add_message_to_room(self.current_room(), _("%s is now ignored") % nick)
+ self.information(_("%s is now ignored") % nick, 'info')
else:
- self.add_message_to_room(self.current_room(), _("%s is already ignored") % nick)
+ self.information(_("%s is alread ignored") % nick, 'info')
def command_unignore(self, arg):
"""
@@ -1038,21 +970,21 @@ class Gui(object):
try:
args = shlex.split(arg)
except ValueError as error:
- return self.add_message_to_room(self.current_room(), _("Error: %s") % (error))
+ return self.information(str(error), _("Error"))
if len(args) != 1:
self.command_help('unignore')
return
- if self.current_room().name == 'Info' or not self.current_room().joined:
+ if not isinstance(self.current_tab(), MucTab):
return
- roomname = self.current_room().name
+ roomname = self.current_tab().get_name()
nick = args[0]
if roomname not in self.ignores or (nick not in self.ignores[roomname]):
- self.add_message_to_room(self.current_room(), _("%s was not ignored") % nick)
+ self.information(_("%s was not ignored") % nick, info)
return
self.ignores[roomname].remove(nick)
- if self.ignores[roomname] == set():
+ if not self.ignores[roomname]:
del self.ignores[roomname]
- self.add_message_to_room(self.current_room(), _("%s is now unignored") % nick)
+ self.information(_("%s is now unignored") % nick, 'info')
def command_away(self, arg):
"""
@@ -1078,25 +1010,27 @@ class Gui(object):
"""
args = arg.split()
reason = None
- room = self.current_room()
- if room.name == 'Info':
+ if not isinstance(self.current_tab(), MucTab) and\
+ not isinstance(self.current_tab(), PrivateTab):
return
+ room = self.current_tab().get_room()
if len(args):
msg = ' '.join(args)
else:
msg = None
- if room.joined:
+ if isinstance(self.current_tab(), MucTab) and\
+ self.current_tab().get_room().joined:
muc.leave_groupchat(self.xmpp, room.name, room.own_nick, arg)
- self.rooms.remove(self.current_room())
+ self.tabs.remove(self.current_tab())
self.refresh_window()
def command_unquery(self, arg):
"""
/unquery
"""
- room = self.current_room()
- if room.jid is not None:
- self.rooms.remove(room)
+ tab = self.current_tab()
+ if isinstance(tab, PrivateTab):
+ self.tabs.remove(tab)
self.refresh_window()
def command_query(self, arg):
@@ -1106,13 +1040,11 @@ class Gui(object):
try:
args = shlex.split(arg)
except ValueError as error:
- return self.add_message_to_room(self.current_room(), _("Error: %s") % (error))
- if len(args) < 1:
+ return self.information(str(error), _("Error"))
+ if len(args) < 1 or not isinstance(self.current_tab(), MucTab):
return
nick = args[0]
- room = self.current_room()
- if room.name == "Info" or room.jid is not None:
- return
+ room = self.current_tab().get_room()
r = None
for user in room.users:
if user.nick == nick:
@@ -1120,19 +1052,19 @@ class Gui(object):
if r and len(args) > 1:
msg = arg[len(nick)+1:]
muc.send_private_message(self.xmpp, r.name, msg)
- self.add_message_to_room(r, msg, None, r.own_nick)
+ self.add_message_to_text_buffer(r, msg, None, r.own_nick)
def command_topic(self, arg):
"""
/topic [new topic]
"""
- room = self.current_room()
+ if not isinstance(self.current_tab(), MucTab):
+ return
+ room = self.current_tab().get_room()
if not arg.strip():
- self.add_message_to_room(room, _("The subject of the room is: %s") % room.topic)
+ self.add_message_to_text_buffer(room, _("The subject of the room is: %s") % room.topic)
return
subject = arg
- if not room.joined or room.name == "Info" and not room.jid:
- return
muc.change_subject(self.xmpp, room.name, subject)
def command_link(self, arg):
@@ -1141,10 +1073,14 @@ class Gui(object):
Opens the link in a browser, or join the room, or add the JID, or
copy it in the clipboard
"""
+ if not isinstance(self.current_tab(), MucTab) and\
+ not isinstance(self.current_tab(), PrivateTab):
+ return
args = arg.split()
if len(args) > 2:
- self.add_message_to_room(self.current_room(),
- _("Link: This command takes at most 2 arguments"))
+ # INFO
+ # self.add_message_to_text_buffer(self.current_room(),
+ # _("Link: This command takes at most 2 arguments"))
return
# set the default parameters
option = "open"
@@ -1159,13 +1095,14 @@ class Gui(object):
try:
nb = int(args[0])
except ValueError:
- self.add_message_to_room(self.current_room(),
- _("Link: 2nd parameter should be a number"))
+ # INFO
+ # self.add_message_to_text_buffer(self.current_room(),
+ # _("Link: 2nd parameter should be a number"))
return
# find the nb-th link in the current buffer
i = 0
link = None
- for msg in self.current_room().messages[:-200:-1]:
+ for msg in self.current_tab().get_room().messages[:-200:-1]:
if not msg:
continue
matches = re.findall('"((ftp|http|https|gopher|mailto|news|nntp|telnet|wais|file|prospero|aim|webcal):(([A-Za-z0-9$_.+!*(),;/?:@&~=-])|%[A-Fa-f0-9]{2}){2,}(#([a-zA-Z0-9][a-zA-Z0-9$_.+!*(),;/?:@&~=%-]*))?([A-Za-z0-9$_+!*();/?:~-]))"', msg.txt)
@@ -1186,8 +1123,13 @@ class Gui(object):
Move the new-messages separator at the bottom on the current
text.
"""
- self.current_room().remove_line_separator()
- self.current_room().add_line_separator()
+ try:
+ room = self.current_tab().get_room()
+ except:
+ return
+ room.remove_line_separator()
+ room.add_line_separator()
+ self.refresh_window()
def command_nick(self, arg):
"""
@@ -1196,22 +1138,23 @@ class Gui(object):
try:
args = shlex.split(arg)
except ValueError as error:
- return self.add_message_to_room(self.current_room(), _("Error: %s") % (error))
+ return self.information(str(error), _("Error"))
+ if not isinstance(self.current_tab(), MucTab):
+ return
if len(args) != 1:
return
nick = args[0]
- room = self.current_room()
+ room = self.current_tab().get_room()
if not room.joined or room.name == "Info":
return
muc.change_nick(self.xmpp, room.name, nick)
- def information(self, msg):
+ def information(self, msg, typ=''):
"""
Displays an informational message in the "Info" room window
"""
- room = self.get_room_by_name("Info")
- self.add_message_to_room(room, msg)
- self.window.input.refresh()
+ self.information_buffer.add_message(msg, nickname=typ)
+ self.refresh_window()
def command_quit(self, arg):
"""
@@ -1221,9 +1164,44 @@ class Gui(object):
msg = arg
else:
msg = None
- for room in self.rooms:
- if not room.jid and room.name != 'Info':
- muc.leave_groupchat(self.xmpp, room.name, room.own_nick, msg)
+ for tab in self.tabs:
+ if isinstance(tab, MucTab):
+ muc.leave_groupchat(self.xmpp, tab.get_room().name, tab.get_room().own_nick, msg)
self.xmpp.disconnect()
self.reset_curses()
sys.exit()
+
+ def do_command(self, key):
+ from common import debug
+ debug('do_command, %s, %s\n' % (key, self.current_tab()))
+ res = self.current_tab().on_input(key)
+ debug('apres, %s\n' % (res))
+ if key in ('^J', '\n'):
+ self.execute(res)
+
+ def execute(self,line):
+ """
+ Execute the /command or just send the line on the current room
+ """
+ if line == "":
+ return
+ if line.startswith('/') and not line.startswith('/me '):
+ command = line.strip()[:].split()[0][1:]
+ arg = line[2+len(command):] # jump the '/' and the ' '
+ # example. on "/link 0 open", command = "link" and arg = "0 open"
+ if command in list(self.commands.keys()):
+ func = self.commands[command][0]
+ func(arg)
+ return
+ else:
+ self.information(_("unknown command (%s)") % (command), _('Error'))
+ else:
+ self.command_say(line)
+
+ def command_say(self, line):
+ if isinstance(self.current_tab(), PrivateTab):
+ muc.send_private_message(self.xmpp, self.current_tab().get_name(), line)
+ 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)
+ doupdate()
diff --git a/src/poezio.py b/src/poezio.py
index 92bb9ae1..4c8c2cdb 100644
--- a/src/poezio.py
+++ b/src/poezio.py
@@ -47,7 +47,7 @@ class MyStdErr(object):
sys.stderr.close()
sys.stderr = self.old_stderr
-my_stderr = MyStdErr(open('/dev/null', 'a'))
+# my_stderr = MyStdErr(open('/dev/null', 'a'))
def exception_handler(type_, value, trace):
"""
@@ -64,7 +64,7 @@ def exception_handler(type_, value, trace):
import os # used to quit the program even from a thread
os.abort()
-sys.excepthook = exception_handler
+# sys.excepthook = exception_handler
import signal
diff --git a/src/room.py b/src/room.py
index 8e5ce87b..cb6fbe43 100644
--- a/src/room.py
+++ b/src/room.py
@@ -14,6 +14,7 @@
# You should have received a copy of the GNU General Public License
# along with Poezio. If not, see <http://www.gnu.org/licenses/>.
+from text_buffer import TextBuffer
from datetime import datetime
from random import randrange
from config import config
@@ -23,36 +24,23 @@ from message import Message
import common
import theme
-class Room(object):
+class Room(TextBuffer):
"""
"""
- number = 0
- def __init__(self, name, nick, window, jid=None):
- self.jid = jid # used for a private chat. None if it's a MUC
+ def __init__(self, name, nick):
+ TextBuffer.__init__(self)
self.name = name
self.own_nick = nick
self.color_state = theme.COLOR_TAB_NORMAL # color used in RoomInfo
- self.nb = Room.number # number used in RoomInfo
- Room.number += 1
self.joined = False # false until self presence is received
self.users = [] # User objects
- self.messages = [] # Message objects
self.topic = ''
- self.window = window
- self.pos = 0 # offset
-
- def scroll_up(self, dist=14):
- # The pos can grow a lot over the top of the number of
- # available lines, it will be fixed on the next refresh of the
- # screen anyway
- self.pos += dist
-
- def scroll_down(self, dist=14):
- self.pos -= dist
- if self.pos <= 0:
- self.pos = 0
def disconnect(self):
+ """
+ Set the state of the room as not joined, so
+ we can know if we can join it, send messages to it, etc
+ """
self.joined = False
def log_message(self, txt, time, nickname):
@@ -89,6 +77,19 @@ class Room(object):
break
return color
+ def get_user_by_name(self, nick):
+ for user in self.users:
+ if user.nick == nick:
+ return user
+ return None
+
+ def set_color_state(self, color):
+ """
+ Set the color that will be used to display the room's
+ number in the RoomInfo window
+ """
+ self.color_state = color
+
def add_message(self, txt, time=None, nickname=None, colorized=False):
"""
Note that user can be None even if nickname is not None. It happens
@@ -103,10 +104,10 @@ class Room(object):
if not time and nickname and\
nickname != self.own_nick and\
self.color_state != theme.COLOR_TAB_CURRENT:
- if not self.jid and self.color_state != theme.COLOR_TAB_HIGHLIGHT:
+ if self.color_state != theme.COLOR_TAB_HIGHLIGHT:
self.set_color_state(theme.COLOR_TAB_NEW_MESSAGE)
- elif self.jid:
- self.set_color_state(theme.COLOR_TAB_PRIVATE)
+ # elif self.jid:
+ # self.set_color_state(theme.COLOR_TAB_PRIVATE)
if not nickname:
color = theme.COLOR_INFORMATION_TEXT
else:
@@ -117,30 +118,3 @@ class Room(object):
if self.pos: # avoid scrolling of one line when one line is received
self.pos += 1
self.messages.append(Message(txt, time, nickname, user, color, colorized))
-
- def remove_line_separator(self):
- """
- Remove the line separator
- """
- if None in self.messages:
- self.messages.remove(None)
-
- def add_line_separator(self):
- """
- add a line separator at the end of messages list
- """
- if None not in self.messages:
- self.messages.append(None)
-
- def get_user_by_name(self, nick):
- for user in self.users:
- if user.nick == nick:
- return user
- return None
-
- def set_color_state(self, color):
- """
- Set the color that will be used to display the room's
- number in the RoomInfo window
- """
- self.color_state = color
diff --git a/src/tab.py b/src/tab.py
new file mode 100644
index 00000000..2b9615b0
--- /dev/null
+++ b/src/tab.py
@@ -0,0 +1,357 @@
+# Copyright 2010 Le Coz Florent <louizatakk@fedoraproject.org>
+#
+# This file is part of Poezio.
+#
+# Poezio is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, version 3 of the License.
+#
+# Poezio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Poezio. If not, see <http://www.gnu.org/licenses/>.
+
+"""
+a Tab object is a way to organize various Window (see window.py)
+around the screen at once.
+A tab is then composed of multiple Window.
+Each Tab object has different refresh() and resize() methods, defining of its
+Window are displayed, etc
+"""
+
+MIN_WIDTH = 50
+MIN_HEIGHT = 10
+
+import window
+import theme
+
+from common import debug
+
+class Tab(object):
+ """
+ """
+ number = 0
+
+ def __init__(self, stdscr):
+ self.nb = Tab.number
+ Tab.number += 1
+ self.size = (self.height, self.width) = stdscr.getmaxyx()
+ if self.height < MIN_HEIGHT or self.width < MIN_WIDTH:
+ self.visible = False
+ else:
+ self.visible = True
+
+ def refresh(self, tabs, informations):
+ """
+ Called on each screen refresh (when something has changed)
+ """
+ raise NotImplementedError
+
+ def resize(self, stdscr):
+ self.size = (self.height, self.width) = stdscr.getmaxyx()
+ if self.height < MIN_HEIGHT or self.width < MIN_WIDTH:
+ self.visible = False
+ else:
+ self.visible = True
+
+ def get_color_state(self):
+ """
+ returns the color that should be used in the GlobalInfoBar
+ """
+ raise NotImplementedError
+
+ def set_color_state(self, color):
+ """
+ set the color state
+ """
+ raise NotImplementedError
+
+ def get_name(self):
+ """
+ get the name of the tab
+ """
+ raise NotImplementedError
+
+ def on_input(self, key):
+ raise NotImplementedError
+
+ def on_lose_focus(self):
+ """
+ called when this tab loses the focus.
+ """
+ raise NotImplementedError
+
+ def on_gain_focus(self):
+ """
+ called when this tab gains the focus.
+ """
+ raise NotImplementedError
+
+ def add_message(self):
+ """
+ Adds a message in the tab.
+ If the tab cannot add a message in itself (for example
+ FormTab, where text is not intented to be appened), it returns False.
+ If the tab can, it returns True
+ """
+ raise NotImplementedError
+
+ def on_scroll_down(self):
+ """
+ Defines what happens when we scrol down
+ """
+ raise NotImplementedError
+
+ def on_scroll_up(self):
+ """
+ Defines what happens when we scrol down
+ """
+ raise NotImplementedError
+
+ def on_info_win_size_changed(self, size, stdscr):
+ """
+ Called when the window with the informations is resized
+ """
+ raise NotImplementedError
+
+class InfoTab(Tab):
+ """
+ The information tab, used to display global informations
+ when using a anonymous account
+ """
+ def __init__(self, stdscr, name):
+ Tab.__init__(self, stdscr)
+ self.tab_win = window.GlobalInfoBar(1, self.width, self.height-2, 0, stdscr, self.visible)
+ self.text_win = window.TextWin(self.height-2, self.width, 0, 0, stdscr, self.visible)
+ self.input = window.Input(1, self.width, self.height-1, 0, stdscr, self.visible)
+ self.name = name
+ self.color_state = theme.COLOR_TAB_NORMAL
+
+ def resize(self, stdscr):
+ Tab.resize(self, stdscr)
+ self.tab_win.resize(1, self.width, self.height-2, 0, stdscr, self.visible)
+ 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):
+ self.text_win.refresh(informations)
+ self.tab_win.refresh(tabs, tabs[0])
+ self.input.refresh()
+
+ def get_name(self):
+ return self.name
+
+ def get_color_state(self):
+ return self.color_state
+
+ def set_color_state(self, color):
+ return
+
+ def on_input(self, key):
+ return self.input.do_command(key)
+
+ def on_lose_focus(self):
+ self.color_state = theme.COLOR_TAB_NORMAL
+
+ def on_gain_focus(self):
+ self.color_state = theme.COLOR_TAB_CURRENT
+
+ def on_scroll_up(self):
+ pass
+
+ def on_scroll_down(self):
+ pass
+
+ def on_info_win_size_changed(self, size, stdscr):
+ return
+
+class MucTab(Tab):
+ """
+ The tab containing a multi-user-chat room.
+ It contains an userlist, an input, a topic, an information and a chat zone
+ """
+ def __init__(self, stdscr, room, info_win_size):
+ """
+ room is a Room object
+ The stdscr is passed to know the size of the
+ terminal
+ """
+ Tab.__init__(self, stdscr)
+ self._room = room
+ self.info_win_size = info_win_size
+ self.topic_win = window.Topic(1, self.width, 0, 0, stdscr, self.visible)
+ self.text_win = window.TextWin(self.height-4-info_win_size, (self.width//10)*9, 1, 0, stdscr, self.visible)
+ self.v_separator = window.VerticalSeparator(self.height-3, 1, 1, 9*(self.width//10), stdscr, self.visible)
+ self.user_win = window.UserList(self.height-3, (self.width//10), 1, 9*(self.width//10)+1, stdscr, self.visible)
+ self.info_header = window.MucInfoWin(1, (self.width//10)*9, self.height-3-self.info_win_size, 0, stdscr, self.visible)
+ self.info_win = window.TextWin(info_win_size, (self.width//10)*9, 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):
+ """
+ Resize the whole window. i.e. all its sub-windows
+ """
+ Tab.resize(self, stdscr)
+ text_width = (self.width//10)*9
+ self.topic_win.resize(1, self.width, 0, 0, stdscr, self.visible)
+ self.text_win.resize(self.height-4-self.info_win_size, text_width, 1, 0, stdscr, self.visible)
+ self.v_separator.resize(self.height-3, 1, 1, 9*(self.width//10), stdscr, self.visible)
+ self.user_win.resize(self.height-3, self.width-text_width-1, 1, text_width+1, stdscr, self.visible)
+ self.info_header.resize(1, (self.width//10)*9, 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)
+ 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.topic_win.refresh(self._room.topic)
+ self.text_win.refresh(self._room)
+ self.v_separator.refresh()
+ self.user_win.refresh(self._room.users)
+ self.info_header.refresh(self._room)
+ self.info_win.refresh(informations)
+ self.tab_win.refresh(tabs, tabs[0])
+ self.input.refresh()
+
+ def on_input(self, key):
+ self.key_func = {
+ "\t": self.completion,
+ "^I": self.completion,
+ "KEY_BTAB": self.last_words_completion,
+ }
+ if key in self.key_func:
+ return self.key_func[key]()
+ return self.input.do_command(key)
+
+ def completion(self):
+ """
+ Called when Tab is pressed, complete the nickname in the input
+ """
+ compare_users = lambda x: x.last_talked
+ self.input.auto_completion([user.nick for user in sorted(self._room.users, key=compare_users, reverse=True)])
+
+ def last_words_completion(self):
+ """
+ Complete the input with words recently said
+ """
+ # build the list of the recent words
+ char_we_dont_want = [',', '(', ')', '.']
+ words = list()
+ for msg in self._room.messages[:-40:-1]:
+ if not msg:
+ continue
+ for char in char_we_dont_want:
+ msg.txt.replace(char, ' ')
+ for word in msg.txt.split():
+ if len(word) > 5:
+ words.append(word)
+ self.input.auto_completion(words, False)
+
+ def get_color_state(self):
+ """
+ """
+ return self._room.color_state
+
+ def set_color_state(self, color):
+ """
+ """
+ self._room.set_color_state(color)
+
+ def get_name(self):
+ """
+ """
+ return self._room.name
+
+ def get_room(self):
+ return self._room
+
+ def on_lose_focus(self):
+ self._room.set_color_state(theme.COLOR_TAB_NORMAL)
+ self._room.remove_line_separator()
+
+ def on_gain_focus(self):
+ self._room.add_line_separator()
+ 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
+ text_width = (self.width//10)*9
+ self.text_win.resize(self.height-4-self.info_win_size, text_width, 1, 0, stdscr, self.visible)
+ self.info_header.resize(1, (self.width//10)*9, 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)
+
+class PrivateTab(Tab):
+ """
+ The tab containg a private conversation (someone from a MUC)
+ """
+ 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.width, 0, 0, stdscr, self.visible)
+ self.info_header = window.PrivateInfoWin(1, self.width, self.height-3-self.info_win_size, 0, stdscr, self.visible)
+ self.info_win = window.TextWin(info_win_size, (self.width//10)*9, 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):
+ 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(info_win_size, (self.width//10)*9, 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()
+
+ def on_gain_focus(self):
+ self._room.add_line_separator()
+ 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
+ text_width = (self.width//10)*9
+ self.text_win.resize(self.height-4-self.info_win_size, text_width, 1, 0, stdscr, self.visible)
+ self.info_header.resize(1, (self.width//10)*9, 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/text_buffer.py b/src/text_buffer.py
new file mode 100644
index 00000000..ad002892
--- /dev/null
+++ b/src/text_buffer.py
@@ -0,0 +1,62 @@
+# Copyright 2010 Le Coz Florent <louizatakk@fedoraproject.org>
+#
+# This file is part of Poezio.
+#
+# Poezio is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, version 3 of the License.
+#
+# Poezio is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Poezio. If not, see <http://www.gnu.org/licenses/>.
+
+from message import Message
+from datetime import datetime
+import theme
+
+class TextBuffer(object):
+ """
+ This class just keep trace of messages, in a list with various
+ informations and attributes.
+ """
+ def __init__(self):
+ self.messages = [] # Message objects
+ self.pos = 0
+
+ def add_message(self, txt, time=None, nickname=None, colorized=False):
+ color = theme.COLOR_NORMAL_TEXT
+ user = None
+ time = time or datetime.now()
+ if self.pos: # avoid scrolling of one line when one line is received
+ self.pos += 1
+ self.messages.append(Message(txt, time, nickname, user, color, colorized))
+
+ def remove_line_separator(self):
+ """
+ Remove the line separator
+ """
+ if None in self.messages:
+ self.messages.remove(None)
+
+ def add_line_separator(self):
+ """
+ add a line separator at the end of messages list
+ """
+ if None not in self.messages:
+ self.messages.append(None)
+
+ def scroll_up(self, dist=14):
+ # The pos can grow a lot over the top of the number of
+ # available lines, it will be fixed on the next refresh of the
+ # screen anyway
+ self.pos += dist
+
+ def scroll_down(self, dist=14):
+ self.pos -= dist
+ if self.pos <= 0:
+ self.pos = 0
+
diff --git a/src/theme.py b/src/theme.py
index 6a0902fe..dda01166 100644
--- a/src/theme.py
+++ b/src/theme.py
@@ -108,6 +108,7 @@ def init_colors():
"""
curses.start_color()
curses.use_default_colors()
+ curses.curs_set(0)
cpt = 0
for i in range(-1, 7):
for y in range(0, 8):
diff --git a/src/window.py b/src/window.py
index 8eff8c15..28229e2f 100644
--- a/src/window.py
+++ b/src/window.py
@@ -48,8 +48,7 @@ class Win(object):
# (°> also, a penguin
# //\
# V_/_
- pass
- self.win.idlok(1)
+ return
self.win.leaveok(1)
def refresh(self):
@@ -98,7 +97,7 @@ class UserList(Win):
role_col = self.color_role[user.role]
show_col = self.color_show[user.show]
self.addstr(y, 0, theme.CHAR_STATUS, curses.color_pair(show_col))
- self.addnstr(y, 1, user.nick, self.width-1, curses.color_pair(role_col))
+ self.addnstr(y, 1, user.nick, self.width-2, curses.color_pair(role_col))
y += 1
if y == self.height:
break
@@ -110,6 +109,9 @@ class UserList(Win):
if not visible:
return
self._resize(height, width, y, x, stdscr)
+ 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))
class Topic(Win):
def __init__(self, height, width, y, x, parent_win, visible):
@@ -119,29 +121,21 @@ class Topic(Win):
def resize(self, height, width, y, x, stdscr, visible):
self._resize(height, width, y, x, stdscr)
- def refresh(self, topic, jid=None):
+ def refresh(self, topic):
if not self.visible:
return
g_lock.acquire()
self.win.erase()
- if not jid:
- self.addnstr(0, 0, topic[:self.width-1], self.width-1, curses.color_pair(theme.COLOR_TOPIC_BAR))
- (y, x) = self.win.getyx()
- remaining_size = self.width - x
- if remaining_size:
- self.addnstr(' '*remaining_size, remaining_size,
- curses.color_pair(theme.COLOR_INFORMATION_BAR))
- elif jid:
- room = jid.split('/')[0]
- nick = '/'.join(jid.split('/')[1:])
- topic = _('%(nick)s from room %(room)s' % {'nick': nick, 'room':room})
- self.addnstr(0, 0, topic + " "*(self.width-len(topic)), self.width-1
- , curses.color_pair(theme.COLOR_PRIVATE_ROOM_BAR))
-
+ self.addnstr(0, 0, topic[:self.width-1], self.width-1, curses.color_pair(theme.COLOR_TOPIC_BAR))
+ (y, x) = self.win.getyx()
+ remaining_size = self.width - x
+ if remaining_size:
+ self.addnstr(' '*remaining_size, remaining_size,
+ curses.color_pair(theme.COLOR_INFORMATION_BAR))
self.win.refresh()
g_lock.release()
-class RoomInfo(Win):
+class GlobalInfoBar(Win):
def __init__(self, height, width, y, x, parent_win, visible):
self.visible = visible
Win.__init__(self, height, width, y, x, parent_win)
@@ -149,17 +143,7 @@ class RoomInfo(Win):
def resize(self, height, width, y, x, stdscr, visible):
self._resize(height, width, y, x, stdscr)
- def print_scroll_position(self, current_room):
- """
- Print, link in Weechat, a -PLUS(n)- where n
- is the number of available lines to scroll
- down
- """
- if current_room.pos > 0:
- plus = ' -PLUS(%s)-' % current_room.pos
- self.addnstr(plus, len(plus), curses.color_pair(theme.COLOR_SCROLLABLE_NUMBER) | curses.A_BOLD)
-
- def refresh(self, rooms, current):
+ def refresh(self, tabs, current):
if not self.visible:
return
def compare_room(a):
@@ -170,17 +154,16 @@ class RoomInfo(Win):
self.win.erase()
self.addnstr(0, 0, "[", self.width
,curses.color_pair(theme.COLOR_INFORMATION_BAR))
- sorted_rooms = sorted(rooms, key=comp)
- for room in sorted_rooms:
- color = room.color_state
+ sorted_tabs = sorted(tabs, key=comp)
+ for tab in sorted_tabs:
+ color = tab.get_color_state()
try:
- self.addstr("%s" % str(room.nb), curses.color_pair(color))
+ self.addstr("%s" % str(tab.nb), curses.color_pair(color))
self.addstr("|", curses.color_pair(theme.COLOR_INFORMATION_BAR))
except: # end of line
break
(y, x) = self.win.getyx()
- self.addnstr(y, x-1, '] '+ current.name, len(current.name)+2, curses.color_pair(theme.COLOR_INFORMATION_BAR))
- self.print_scroll_position(current)
+ self.addnstr(y, x-1, '] ', 2, curses.color_pair(theme.COLOR_INFORMATION_BAR))
(y, x) = self.win.getyx()
remaining_size = self.width - x
self.addnstr(' '*remaining_size, remaining_size,
@@ -188,6 +171,132 @@ class RoomInfo(Win):
self.win.refresh()
g_lock.release()
+class InfoWin(Win):
+ """
+ Base class for all the *InfoWin, used in various tabs. For example
+ MucInfoWin, etc. Provides some useful methods.
+ """
+ def __init__(self, height, width, y, x, parent_win, visible):
+ self.visible = visible
+ Win.__init__(self, height, width, y, x, parent_win)
+
+ def print_scroll_position(self, text_buffer):
+ """
+ Print, link in Weechat, a -PLUS(n)- where n
+ is the number of available lines to scroll
+ down
+ """
+ if text_buffer.pos > 0:
+ 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
+ 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)
+
+ 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()
+ 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 = ' from room %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
+ about the MUC we are viewing
+ """
+ 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)
+
+ def refresh(self, room):
+ if not self.visible:
+ return
+ g_lock.acquire()
+ self.win.erase()
+ self.write_room_name(room)
+ self.write_own_nick(room)
+ self.write_disconnected(room)
+ self.write_role(room)
+ self.print_scroll_position(room)
+ self.finish_line()
+ self.win.refresh()
+ g_lock.release()
+
+ def write_room_name(self, room):
+ """
+ """
+ self.addnstr('[', 1, curses.color_pair(theme.COLOR_INFORMATION_BAR))
+ self.addnstr(room.name, len(room.name), curses.color_pair(13))
+ self.addnstr('] ', 2, curses.color_pair(theme.COLOR_INFORMATION_BAR))
+
+ def write_disconnected(self, room):
+ """
+ Shows a message if the room is not joined
+ """
+ if not room.joined:
+ self.addnstr(' -!- Not connected ', 21, curses.color_pair(theme.COLOR_INFORMATION_BAR))
+ def write_own_nick(self, room):
+ """
+ Write our own nick in the info bar
+ """
+ nick = room.own_nick
+ if not nick:
+ return
+ if len(nick) > 13:
+ nick = nick[:13]+'…'
+ length = 14
+ else:
+ length = len(nick)
+ self.addnstr(nick, length, curses.color_pair(theme.COLOR_INFORMATION_BAR))
+
+ def write_role(self, room):
+ """
+ Write our own role and affiliation
+ """
+ from common import debug
+
+ own_user = None
+ for user in room.users:
+ if user.nick == room.own_nick:
+ own_user = user
+ break
+ if not own_user:
+ return
+ txt = ' ('
+ if own_user.affiliation != 'none':
+ txt += own_user.affiliation+', '
+ txt += own_user.role+')'
+ self.addnstr(txt, len(txt), curses.color_pair(theme.COLOR_INFORMATION_BAR))
+
class TextWin(Win):
"""
Just keep ONE single window for the text area and rewrite EVERYTHING
@@ -235,7 +344,7 @@ class TextWin(Win):
limit = txt[:self.width-offset].rfind(' ')
this_line_was_broken_by_space = True
if limit <= 0:
- limit = self.width-offset-1
+ limit = self.width-offset
this_line_was_broken_by_space = False
else:
limit = self.width-offset-1
@@ -269,6 +378,8 @@ class TextWin(Win):
"""
if not self.visible:
return
+ if self.height <= 0:
+ return
g_lock.acquire()
self.win.erase()
lines = self.build_lines_from_messages(room.messages)
@@ -300,7 +411,7 @@ class TextWin(Win):
"""
"""
self.win.attron(curses.color_pair(theme.COLOR_NEW_TEXT_SEPARATOR))
- self.addstr(' -'*(self.width//2))
+ self.addstr(' -'*(self.width//2-1))
self.win.attroff(curses.color_pair(theme.COLOR_NEW_TEXT_SEPARATOR))
def write_text(self, y, x, txt, color, colorized):
@@ -322,7 +433,12 @@ class TextWin(Win):
theme.CHAR_QUIT: theme.COLOR_QUIT_CHAR,
theme.CHAR_KICK: theme.COLOR_KICK_CHAR,
}
- for word in shlex.split(txt):
+ try:
+ splitted = shlex.split(txt)
+ except ValueError:
+ txt += '"'
+ splitted = shlex.split(txt)
+ for word in splitted:
if word in list(special_words.keys()):
self.addstr(word, curses.color_pair(special_words[word]))
elif word.startswith('(') and word.endswith(')'):
@@ -392,9 +508,34 @@ class Input(Win):
The line where text is entered
"""
def __init__(self, height, width, y, x, stdscr, visible):
+ self.key_func = {
+ "KEY_LEFT": self.key_left,
+ "M-D": self.key_left,
+ "KEY_RIGHT": self.key_right,
+ "M-C": self.key_right,
+ "KEY_UP": self.key_up,
+ "M-A": self.key_up,
+ "KEY_END": self.key_end,
+ "KEY_HOME": self.key_home,
+ "KEY_DOWN": self.key_down,
+ "M-B": self.key_down,
+ "KEY_DC": self.key_dc,
+ '^D': self.key_dc,
+ 'M-b': self.jump_word_left,
+ '^W': self.delete_word,
+ '^K': self.delete_end_of_line,
+ '^U': self.delete_begining_of_line,
+ '^Y': self.paste_clipboard,
+ '^A': self.key_home,
+ '^E': self.key_end,
+ 'M-f': self.jump_word_right,
+ "KEY_BACKSPACE": self.key_backspace,
+ '^?': self.key_backspace,
+ '^J': self.get_text,
+ '\n': self.get_text,
+ }
+
Win.__init__(self, height, width, y, x, stdscr)
- curses.curs_set(1)
- self.win.leaveok(0)
self.visible = visible
self.history = []
self.text = ''
@@ -412,7 +553,6 @@ class Input(Win):
if not visible:
return
self._resize(height, width, y, x, stdscr)
- self.win.leaveok(0)
self.win.clear()
self.addnstr(0, 0, self.text, self.width-1)
@@ -686,6 +826,10 @@ class Input(Win):
self.key_end(False)
def do_command(self, key, reset=True):
+ if key in self.key_func:
+ return self.key_func[key]()
+ # if not key or len(key) > 1:
+ # return # ignore non-handled keyboard shortcuts
self.reset_completion()
self.text = self.text[:self.pos+self.line_pos]+key+self.text[self.pos+self.line_pos:]
(y, x) = self.win.getyx()
@@ -707,6 +851,7 @@ class Input(Win):
if len(txt) != 0:
self.history.append(txt)
self.histo_pos = len(self.history)-1
+ self.rewrite_text()
return txt
def rewrite_text(self):
@@ -716,14 +861,14 @@ class Input(Win):
g_lock.acquire()
self.clear_text()
self.addstr(self.text[self.line_pos:self.line_pos+self.width-1])
- self.win.move(0, self.pos)
- self.refresh()
+ self.win.chgat(0, self.pos, 1, curses.A_REVERSE)
+ self.win.refresh()
g_lock.release()
def refresh(self):
if not self.visible:
return
- self.win.refresh()
+ self.rewrite_text()
def clear_text(self):
self.win.erase()
@@ -733,61 +878,39 @@ class Window(object):
The whole "screen" that can be seen at once in the terminal.
It contains an userlist, an input zone, a topic zone and a chat zone
"""
- def __init__(self, stdscr):
- """
- name is the name of the Tab, and it's also
- the JID of the chatroom.
- A particular tab is the "Info" tab which has no
- name (None). This info tab should be unique.
- The stdscr should be passed to know the size of the
- terminal
- """
- self.size = (self.height, self.width) = stdscr.getmaxyx()
- if self.height < 10 or self.width < 60:
- visible = False
- else:
- visible = True
- if visible:
- stdscr.attron(curses.color_pair(theme.COLOR_VERTICAL_SEPARATOR))
- stdscr.vline(1, 9*(self.width//10), curses.ACS_VLINE, self.height-2)
- stdscr.attroff(curses.color_pair(theme.COLOR_VERTICAL_SEPARATOR))
- self.user_win = UserList(self.height-3, (self.width//10)-1, 1, 9*(self.width//10)+1, stdscr, visible)
- self.topic_win = Topic(1, self.width, 0, 0, stdscr, visible)
- self.info_win = RoomInfo(1, self.width, self.height-2, 0, stdscr, visible)
- self.text_win = TextWin(self.height-3, (self.width//10)*9, 1, 0, stdscr, visible)
- self.input = Input(1, self.width, self.height-1, 0, stdscr, visible)
-
- def resize(self, stdscr):
- """
- Resize the whole window. i.e. all its sub-windows
- """
- self.size = (self.height, self.width) = stdscr.getmaxyx()
- if self.height < 10 or self.width < 50:
- visible = False
- else:
- visible = True
- if visible:
- stdscr.attron(curses.color_pair(theme.COLOR_VERTICAL_SEPARATOR))
- stdscr.vline(1, 9*(self.width//10), curses.ACS_VLINE, self.height-2)
- stdscr.attroff(curses.color_pair(theme.COLOR_VERTICAL_SEPARATOR))
- text_width = (self.width//10)*9;
- self.topic_win.resize(1, self.width, 0, 0, stdscr, visible)
- self.info_win.resize(1, self.width, self.height-2, 0, stdscr, visible)
- self.text_win.resize(self.height-3, text_width, 1, 0, stdscr, visible)
- self.user_win.resize(self.height-3, self.width-text_width-1, 1, text_width+1, stdscr, visible)
- self.input.resize(1, self.width, self.height-1, 0, stdscr, visible)
-
- def refresh(self, rooms):
- """
- 'room' is the current one
- """
- room = rooms[0] # get current room
- self.text_win.refresh(room)
- self.user_win.refresh(room.users)
- self.topic_win.refresh(room.topic, room.jid)
- self.info_win.refresh(rooms, room)
- self.input.refresh()
-
- def do_command(self, key):
- self.input.do_command(key)
- self.input.refresh()
+
+
+ # TODO JidWindow
+ # elif jid:
+ # room = jid.split('/')[0]
+ # nick = '/'.join(jid.split('/')[1:])
+ # topic = _('%(nick)s from room %(room)s' % {'nick': nick, 'room':room})
+ # self.addnstr(0, 0, topic + " "*(self.width-len(topic)), self.width-1
+ # , curses.color_pair(theme.COLOR_PRIVATE_ROOM_BAR))
+
+class VerticalSeparator(Win):
+ """
+ Just a one-column window, with just a line in it, that is
+ refreshed only on resize, but never on refresh, for efficiency
+ """
+ def __init__(self, height, width, y, x, parent_win, visible):
+ Win.__init__(self, height, width, y, x, parent_win)
+ self.visible = visible
+
+ def rewrite_line(self):
+ g_lock.acquire()
+ self.win.vline(0, 0, curses.ACS_VLINE, self.height, curses.color_pair(theme.COLOR_VERTICAL_SEPARATOR))
+ self.win.refresh()
+ g_lock.release()
+
+ def resize(self, height, width, y, x, stdscr, visible):
+ self.visible = visible
+ self._resize(height, width, y, x, stdscr)
+ if not visible:
+ return
+ self.rewrite_line()
+
+ def refresh(self):
+ if not self.visible:
+ return
+ self.rewrite_line()