complete refactoring of the ui. Everything is now very modulable. Little info...

complete refactoring of the ui. Everything is now very modulable. Little info win added at the bottom. Roster is ready to be implemented.
parent 29e52509
This diff is collapsed.
......@@ -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
......
......@@ -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
# 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
# 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
......@@ -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):
......
......@@ -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.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
"""