summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--data/themes/dark81
-rw-r--r--data/themes/dark.py35
-rw-r--r--data/themes/poezio83
-rw-r--r--src/common.py5
-rw-r--r--src/core.py39
-rw-r--r--src/room.py30
-rw-r--r--src/tabs.py156
-rw-r--r--src/text_buffer.py1
-rw-r--r--src/theme.py173
-rw-r--r--src/theming.py265
-rw-r--r--src/user.py8
-rw-r--r--src/windows.py241
-rw-r--r--src/xhtml.py216
13 files changed, 727 insertions, 606 deletions
diff --git a/data/themes/dark b/data/themes/dark
deleted file mode 100644
index 77093577..00000000
--- a/data/themes/dark
+++ /dev/null
@@ -1,81 +0,0 @@
-# A dark theme file.
-# For more informations, see http://dev.louiz.org/project/poezio/doc/TheThemes
-
-# Message text color
-COLOR_NORMAL_TEXT = 0
-COLOR_INFORMATION_TEXT = 5
-COLOR_HIGHLIGHT_NICK = 10
-
-# User list color
-COLOR_USER_VISITOR = 7
-COLOR_USER_PARTICIPANT = 4
-COLOR_USER_NONE = 0
-COLOR_USER_MODERATOR = 1
-
-# The character printed in color (COLOR_STATUS_*) before the nickname
-# in the user list
-CHAR_STATUS = '|'
-
-# Separators
-COLOR_VERTICAL_SEPARATOR = 0
-COLOR_NEW_TEXT_SEPARATOR = 2
-COLOR_MORE_INDICATOR = 3
-
-# Time
-COLOR_TIME_SEPARATOR = 6
-COLOR_TIME_LIMITER = 0
-CHAR_TIME_LEFT = ''
-CHAR_TIME_RIGHT = ''
-COLOR_TIME_NUMBERS = 0
-
-# Tabs
-COLOR_TAB_NORMAL = 57
-COLOR_TAB_CURRENT = 7
-COLOR_TAB_NEW_MESSAGE = 10
-COLOR_TAB_HIGHLIGHT = 8
-COLOR_TAB_PRIVATE = 9
-COLOR_TAB_DISCONNECTED = 30
-
-# Nickname colors
-LIST_COLOR_NICKNAMES = [
- 1, 2, 3, 4, 5, 6, -2, -4, -5, -6
- ]
-COLOR_OWN_NICK = 7
-
-# Status color
-COLOR_STATUS_XA = 5
-COLOR_STATUS_NONE = 4
-COLOR_STATUS_DND = 1
-COLOR_STATUS_AWAY = 3
-COLOR_STATUS_CHAT = 2
-COLOR_STATUS_UNAVAILABLE = 57
-COLOR_STATUS_ONLINE = 41
-
-# Bars
-COLOR_INFORMATION_BAR = 57
-COLOR_TOPIC_BAR = 14
-COLOR_PRIVATE_ROOM_BAR = 9
-COLOR_SCROLLABLE_NUMBER = 10
-COLOR_SELECTED_ROW = 14
-COLOR_PRIVATE_NAME = 13
-COLOR_CONVERSATION_NAME = 10
-COLOR_GROUPCHAT_NAME = 13
-COLOR_COLUMN_HEADER = 13
-
-# Strings for special messages (like join, quit, nick change, etc)
-
-# Special messages
-CHAR_JOIN = '---->'
-CHAR_QUIT = '<----'
-CHAR_KICK = '-!-'
-
-COLOR_JOIN_CHAR = 4
-COLOR_QUIT_CHAR = 1
-COLOR_KICK_CHAR = 1
-
-# words between ()
-COLOR_CURLYBRACKETED_WORD = 4
-# words between {}
-COLOR_ACCOLADE_WORD = 6
-# words between []
-COLOR_BRACKETED_WORD = 3
diff --git a/data/themes/dark.py b/data/themes/dark.py
new file mode 100644
index 00000000..bbe226f8
--- /dev/null
+++ b/data/themes/dark.py
@@ -0,0 +1,35 @@
+import theming
+
+class DarkTheme(theming.Theme):
+ COLOR_INFORMATION_BAR = (-1, 236)
+ COLOR_STATUS_XA = (53, -1)
+ COLOR_STATUS_AWAY = (214, -1)
+ COLOR_STATUS_DND = (160, -1)
+ COLOR_STATUS_CHAT = (34 , -1)
+ COLOR_STATUS_UNAVAILABLE = (242 , -1)
+ COLOR_STATUS_ONLINE = (27 , -1)
+
+ COLOR_VERTICAL_SEPARATOR = (236, -1)
+ COLOR_NEW_TEXT_SEPARATOR = (213, -1)
+ COLOR_MORE_INDICATOR = (6, 4)
+
+ COLOR_HIGHLIGHT_NICK = (236, 202, 'b')
+
+ COLOR_TAB_NORMAL = (-1, 236)
+ COLOR_TAB_CURRENT = (-1, 16)
+ COLOR_TAB_NEW_MESSAGE = (3, 236)
+ COLOR_TAB_HIGHLIGHT = (1, 236)
+ COLOR_TAB_PRIVATE = (2, 236)
+ COLOR_TAB_DISCONNECTED = (13, 236)
+
+ COLOR_TOPIC_BAR = (-1, 236)
+ COLOR_SCROLLABLE_NUMBER = (220, 236, 'b')
+ COLOR_SELECTED_ROW = (-1, 238)
+ COLOR_PRIVATE_NAME = (173, 236)
+ COLOR_CONVERSATION_NAME = (2, 236)
+ COLOR_GROUPCHAT_NAME = (106, 236)
+ COLOR_COLUMN_HEADER = (36, 236)
+
+theme = DarkTheme()
+
+
diff --git a/data/themes/poezio b/data/themes/poezio
deleted file mode 100644
index b5bb6a66..00000000
--- a/data/themes/poezio
+++ /dev/null
@@ -1,83 +0,0 @@
-# A theme file. (the Default one)
-# For more informations, see http://dev.louiz.org/project/poezio/doc/TheThemes
-
-# Message text color
-COLOR_NORMAL_TEXT = 0
-COLOR_INFORMATION_TEXT = 5
-COLOR_HIGHLIGHT_NICK = -46
-
-# User list color
-COLOR_USER_VISITOR = 7
-COLOR_USER_PARTICIPANT = 4
-COLOR_USER_NONE = 0
-COLOR_USER_MODERATOR = 1
-
-# nickname colors
-COLOR_REMOTE_USER = 5
-
-# The character printed in color (COLOR_STATUS_*) before the nickname
-# in the user list
-CHAR_STATUS = ' '
-
-# Separators
-COLOR_VERTICAL_SEPARATOR = 4
-COLOR_NEW_TEXT_SEPARATOR = 2
-COLOR_MORE_INDICATOR = 6
-
-# Time
-COLOR_TIME_SEPARATOR = 6
-COLOR_TIME_LIMITER = 0
-CHAR_TIME_LEFT = ''
-CHAR_TIME_RIGHT = ''
-COLOR_TIME_NUMBERS = 0
-
-# Tabs
-COLOR_TAB_NORMAL = 42
-COLOR_TAB_CURRENT = 56
-COLOR_TAB_NEW_MESSAGE = 49
-COLOR_TAB_HIGHLIGHT = 21
-COLOR_TAB_PRIVATE = 28
-COLOR_TAB_DISCONNECTED = 30
-
-# Nickname colors
-LIST_COLOR_NICKNAMES = [
- 1, 2, 3, 4, 5, 6, -2, -4, -5, -6
- ]
-COLOR_OWN_NICK = 7
-
-# Status color
-COLOR_STATUS_XA = 49
-COLOR_STATUS_NONE = 0
-COLOR_STATUS_DND = 21
-COLOR_STATUS_AWAY = 35
-COLOR_STATUS_CHAT = 28
-COLOR_STATUS_UNAVAILABLE = 57
-COLOR_STATUS_ONLINE = 41
-
-# Bars
-COLOR_INFORMATION_BAR = 42
-COLOR_TOPIC_BAR = 42
-COLOR_PRIVATE_ROOM_BAR = 28
-COLOR_SCROLLABLE_NUMBER = 39
-COLOR_SELECTED_ROW = 42
-COLOR_PRIVATE_NAME = 42
-COLOR_CONVERSATION_NAME = 42
-COLOR_GROUPCHAT_NAME = 42
-COLOR_COLUMN_HEADER = 36
-
-# Strings for special messages (like join, quit, nick change, etc)
-# Special messages
-CHAR_JOIN = '---->'
-CHAR_QUIT = '<----'
-CHAR_KICK = '-!-'
-
-COLOR_JOIN_CHAR = 4
-COLOR_QUIT_CHAR = 1
-COLOR_KICK_CHAR = 1
-
-# words between ()
-COLOR_CURLYBRACKETED_WORD = 4
-# words between {}
-COLOR_ACCOLADE_WORD = 6
-# words between []
-COLOR_BRACKETED_WORD = 3
diff --git a/src/common.py b/src/common.py
index db750b30..11a09b93 100644
--- a/src/common.py
+++ b/src/common.py
@@ -186,11 +186,6 @@ def shell_split(st):
except ValueError:
return st.split(" ")
-def curses_color_pair(color):
- if color < 0:
- return curses.color_pair(-color) | curses.A_BOLD
- return curses.color_pair(color)
-
def replace_key_with_bound(key):
if config.has_option('bindings', key):
return config.get(key, key, 'bindings')
diff --git a/src/core.py b/src/core.py
index 0b8442ff..88c926a9 100644
--- a/src/core.py
+++ b/src/core.py
@@ -18,7 +18,7 @@ import traceback
from datetime import datetime
import common
-import theme
+import theming
import logging
import singleton
import collections
@@ -47,6 +47,7 @@ from roster import Roster, RosterGroup, roster
from contact import Contact, Resource
from text_buffer import TextBuffer
from keyboard import read_char
+from theming import get_theme
# http://xmpp.org/extensions/xep-0045.html#errorstatus
ERROR_AND_STATUS_CODES = {
@@ -370,13 +371,13 @@ class Core(object):
return
# If a resource got offline, display the message in the conversation with this
# precise resource.
- self.add_information_message_to_conversation_tab(jid.full, '\x195%s is \x191offline' % (resource.get_jid().full))
+ self.add_information_message_to_conversation_tab(jid.full, '\x195}%s is \x191}offline' % (resource.get_jid().full))
contact.remove_resource(resource)
# Display the message in the conversation with the bare JID only if that was
# the only resource online (i.e. now the contact is completely disconnected)
if not contact.get_highest_priority_resource(): # No resource left: that was the last one
- self.add_information_message_to_conversation_tab(jid.bare, '\x195%s is \x191offline' % (jid.bare))
- self.information('\x193%s \x195is \x191offline' % (resource.get_jid().bare), "Roster")
+ self.add_information_message_to_conversation_tab(jid.bare, '\x195}%s is \x191}offline' % (jid.bare))
+ self.information('\x193}%s \x195}is \x191}offline' % (resource.get_jid().bare), "Roster")
def on_got_online(self, presence):
jid = presence['from']
@@ -394,13 +395,13 @@ class Core(object):
resource.set_status(status_message)
resource.set_presence(status)
resource.set_priority(priority)
- self.add_information_message_to_conversation_tab(jid.full, '\x195%s is \x194online' % (jid.full))
+ self.add_information_message_to_conversation_tab(jid.full, '\x195}%s is \x194}online' % (jid.full))
if not contact.get_highest_priority_resource():
# No connected resource yet: the user's just connecting
if time.time() - self.connection_time > 12:
# We do not display messages if we recently logged in
- self.information("\x193%s \x195is \x194online\x195 (\x190%s\x195)" % (resource.get_jid().bare, status), "Roster")
- self.add_information_message_to_conversation_tab(jid.bare, '\x195%s is \x194online' % (jid.bare))
+ self.information("\x193}%s \x195}is \x194}online\x195} (\x190}%s\x195})" % (resource.get_jid().bare, status), "Roster")
+ self.add_information_message_to_conversation_tab(jid.bare, '\x195}%s is \x194}online' % (jid.bare))
contact.add_resource(resource)
def add_information_message_to_conversation_tab(self, jid, msg):
@@ -614,7 +615,7 @@ class Core(object):
remote_nick = roster.get_contact_by_jid(jid.bare).get_name() or jid.user
else:
remote_nick = jid.user
- conversation.get_room().add_message(body, nickname=remote_nick, nick_color=theme.COLOR_REMOTE_USER)
+ conversation.get_room().add_message(body, nickname=remote_nick, nick_color=get_theme().COLOR_REMOTE_USER)
if conversation.remote_wants_chatstates is None:
if message['chat_state']:
conversation.remote_wants_chatstates = True
@@ -624,7 +625,7 @@ class Core(object):
if 'private' in config.get('beep_on', 'highlight private').split():
curses.beep()
if self.current_tab() is not conversation:
- conversation.set_color_state(theme.COLOR_TAB_PRIVATE)
+ conversation.set_color_state(get_theme().COLOR_TAB_PRIVATE)
self.refresh_tab_win()
else:
self.refresh_window()
@@ -686,7 +687,7 @@ class Core(object):
roster.add_contact(contact, jid)
roster.edit_groups_of_contact(contact, [])
contact.set_ask('asked')
- self.get_tab_by_number(0).set_color_state(theme.COLOR_TAB_HIGHLIGHT)
+ self.get_tab_by_number(0).set_color_state(get_theme().COLOR_TAB_HIGHLIGHT)
self.information('%s wants to subscribe to your presence'%jid, 'Roster')
if isinstance(self.current_tab(), tabs.RosterInfoTab):
self.refresh_window()
@@ -815,9 +816,11 @@ class Core(object):
curses.noecho()
curses.nonl()
curses.raw()
- theme.init_colors()
stdscr.idlok(True)
stdscr.keypad(True)
+ curses.start_color()
+ curses.use_default_colors()
+ theming.reload_theme()
curses.ungetch(" ") # H4X: without this, the screen is
stdscr.getkey() # erased on the first "getkey()"
@@ -835,7 +838,7 @@ class Core(object):
"""
Refresh everything
"""
- self.current_tab().set_color_state(theme.COLOR_TAB_CURRENT)
+ self.current_tab().set_color_state(get_theme().COLOR_TAB_CURRENT)
self.current_tab().refresh()
self.doupdate()
@@ -883,19 +886,19 @@ class Core(object):
- A Muc with any new message
"""
for tab in self.tabs:
- if tab.get_color_state() == theme.COLOR_TAB_PRIVATE:
+ if tab.get_color_state() == get_theme().COLOR_TAB_PRIVATE:
self.command_win('%s' % tab.nb)
return
for tab in self.tabs:
- if tab.get_color_state() == theme.COLOR_TAB_HIGHLIGHT:
+ if tab.get_color_state() == get_theme().COLOR_TAB_HIGHLIGHT:
self.command_win('%s' % tab.nb)
return
for tab in self.tabs:
- if tab.get_color_state() == theme.COLOR_TAB_NEW_MESSAGE:
+ if tab.get_color_state() == get_theme().COLOR_TAB_NEW_MESSAGE:
self.command_win('%s' % tab.nb)
return
for tab in self.tabs:
- if tab.get_color_state() == theme.COLOR_TAB_DISCONNECTED:
+ if tab.get_color_state() == get_theme().COLOR_TAB_DISCONNECTED:
self.command_win('%s' % tab.nb)
return
for tab in self.tabs:
@@ -1210,9 +1213,7 @@ class Core(object):
self.xmpp.plugin['xep_0030'].get_items(jid=server, block=False, callback=list_tab.on_muc_list_item_received)
def command_theme(self, arg):
- """
- """
- theme.reload_theme()
+ theming.reload_theme()
self.refresh_window()
def command_win(self, arg):
diff --git a/src/room.py b/src/room.py
index 83b00e62..b97dd0b6 100644
--- a/src/room.py
+++ b/src/room.py
@@ -12,7 +12,7 @@ from config import config
from logger import logger
import common
-import theme
+from theming import get_theme
import logging
import curses
@@ -24,7 +24,7 @@ class Room(TextBuffer):
TextBuffer.__init__(self, messages_nb_limit)
self.name = name
self.own_nick = nick
- self.color_state = theme.COLOR_TAB_NORMAL # color used in RoomInfo
+ self.color_state = get_theme().COLOR_TAB_NORMAL # color used in RoomInfo
self.joined = False # false until self presence is receied
self.users = [] # User objects
self.topic = ''
@@ -35,7 +35,7 @@ class Room(TextBuffer):
we can know if we can join it, send messages to it, etc
"""
self.users = []
- self.color_state = theme.COLOR_TAB_DISCONNECTED
+ self.color_state = get_theme().COLOR_TAB_DISCONNECTED
self.joined = False
def get_single_line_topic(self):
@@ -59,16 +59,16 @@ class Room(TextBuffer):
color = None
if not time and nickname and nickname != self.own_nick and self.joined:
if self.own_nick.lower() in txt.lower():
- if self.color_state != theme.COLOR_TAB_CURRENT:
- self.set_color_state(theme.COLOR_TAB_HIGHLIGHT)
- color = theme.COLOR_HIGHLIGHT_NICK
+ if self.color_state != get_theme().COLOR_TAB_CURRENT:
+ self.set_color_state(get_theme().COLOR_TAB_HIGHLIGHT)
+ color = get_theme().COLOR_HIGHLIGHT_NICK
else:
highlight_words = config.get('highlight_on', '').split(':')
for word in highlight_words:
if word and word.lower() in txt.lower():
- if self.color_state != theme.COLOR_TAB_CURRENT:
- self.set_color_state(theme.COLOR_TAB_HIGHLIGHT)
- color = theme.COLOR_HIGHLIGHT_NICK
+ if self.color_state != get_theme().COLOR_TAB_CURRENT:
+ self.set_color_state(get_theme().COLOR_TAB_HIGHLIGHT)
+ color = get_theme().COLOR_HIGHLIGHT_NICK
break
if color:
beep_on = config.get('beep_on', 'highlight private').split()
@@ -101,7 +101,7 @@ class Room(TextBuffer):
self.log_message(txt, time, nickname)
special_message = False
if txt.startswith('/me '):
- txt = "\x192* \x195" + nickname + ' ' + txt[4:]
+ txt = "\x192}* \x195}" + nickname + ' ' + txt[4:]
special_message = True
user = self.get_user_by_name(nickname) if nickname is not None else None
if user:
@@ -110,18 +110,18 @@ class Room(TextBuffer):
user = forced_user
if not time and nickname and\
nickname != self.own_nick and\
- self.color_state != theme.COLOR_TAB_CURRENT:
- if self.color_state != theme.COLOR_TAB_HIGHLIGHT:
- self.set_color_state(theme.COLOR_TAB_NEW_MESSAGE)
+ self.color_state != get_theme().COLOR_TAB_CURRENT:
+ if self.color_state != get_theme().COLOR_TAB_HIGHLIGHT:
+ self.set_color_state(get_theme().COLOR_TAB_NEW_MESSAGE)
nick_color = nick_color or None
if not nickname or time:
- txt = '\x195%s' % (txt,)
+ txt = '\x195}%s' % (txt,)
else: # TODO
highlight = self.do_highlight(txt, time, nickname)
if highlight:
nick_color = highlight
if special_message:
- txt = '\x195%s' % (txt,)
+ txt = '\x195}%s' % (txt,)
nickname = None
time = time or datetime.now()
message = Message(txt='%s\x19o'%(txt.replace('\t', ' '),), nick_color=nick_color,
diff --git a/src/tabs.py b/src/tabs.py
index 24f347ba..88d8ad7f 100644
--- a/src/tabs.py
+++ b/src/tabs.py
@@ -13,13 +13,15 @@ Each Tab object has different refresh() and resize() methods, defining how its
Windows are displayed, resized, etc
"""
+MIN_WIDTH = 50
+MIN_HEIGHT = 22
+
import logging
log = logging.getLogger(__name__)
from gettext import gettext as _
import windows
-import theme
import curses
import difflib
import text_buffer
@@ -33,6 +35,8 @@ import timed_events
import multiuserchat as muc
+from theming import get_theme
+
from sleekxmpp.xmlstream.stanzabase import JID
from config import config
from roster import RosterGroup, roster
@@ -58,7 +62,7 @@ class Tab(object):
tab_core = None
def __init__(self):
self.input = None
- self._color_state = theme.COLOR_TAB_NORMAL
+ self._color_state = get_theme().COLOR_TAB_NORMAL
self.need_resize = False
self.nb = Tab.number
Tab.number += 1
@@ -86,7 +90,10 @@ class Tab(object):
@staticmethod
def resize(scr):
Tab.size = (Tab.height, Tab.width) = scr.getmaxyx()
- Tab.visible = True
+ if Tab.height < MIN_HEIGHT or Tab.width < MIN_WIDTH:
+ Tab.visible = False
+ else:
+ Tab.visible = True
def complete_commands(self, the_input):
"""
@@ -185,13 +192,13 @@ class Tab(object):
"""
called when this tab loses the focus.
"""
- self._color_state = theme.COLOR_TAB_NORMAL
+ self._color_state = get_theme().COLOR_TAB_NORMAL
def on_gain_focus(self):
"""
called when this tab gains the focus.
"""
- self._color_state = theme.COLOR_TAB_CURRENT
+ self._color_state = get_theme().COLOR_TAB_CURRENT
def add_message(self):
"""
@@ -467,9 +474,9 @@ class MucTab(ChatTab):
for user in users:
if user.nick == room.own_nick:
users.remove(user)
- nb_color = len(theme.LIST_COLOR_NICKNAMES)
+ nb_color = len(get_theme().LIST_COLOR_NICKNAMES)
for i, user in enumerate(sorted(users, key=compare_users, reverse=True)):
- user.color = theme.LIST_COLOR_NICKNAMES[i % nb_color]
+ user.color = get_theme().LIST_COLOR_NICKNAMES[i % nb_color]
self.text_win.rebuild_everything(self._room)
self.text_win.refresh(self._room)
self.input.refresh()
@@ -674,7 +681,6 @@ class MucTab(ChatTab):
"""
if not self.visible:
return
- self.need_resize = False
text_width = (self.width//10)*9
self.topic_win.resize(1, self.width, 0, 0)
self.v_separator.resize(self.height-3, 1, 1, 9*(self.width//10))
@@ -744,14 +750,14 @@ class MucTab(ChatTab):
return self._room
def on_lose_focus(self):
- self._room.set_color_state(theme.COLOR_TAB_NORMAL)
+ self._room.set_color_state(get_theme().COLOR_TAB_NORMAL)
self.text_win.remove_line_separator()
self.text_win.add_line_separator()
if config.get('send_chat_states', 'true') == 'true' and not self.input.get_text():
self.send_chat_state('inactive')
def on_gain_focus(self):
- self._room.set_color_state(theme.COLOR_TAB_CURRENT)
+ self._room.set_color_state(get_theme().COLOR_TAB_CURRENT)
if self.text_win.built_lines and self.text_win.built_lines[-1] is None:
self.text_win.remove_line_separator()
curses.curs_set(1)
@@ -794,10 +800,10 @@ class MucTab(ChatTab):
if from_nick == room.own_nick:
room.joined = True
self.send_chat_state('active')
- new_user.color = theme.COLOR_OWN_NICK
- room.add_message(_("\x195Your nickname is \x193%s") % (from_nick))
+ new_user.color = get_theme().COLOR_OWN_NICK
+ room.add_message(_("\x195}Your nickname is \x193}%s") % (from_nick))
if '170' in status_codes:
- room.add_message('\x191Warning: \x195this room is publicly logged')
+ room.add_message('\x191}Warning: \x195}this room is publicly logged')
else:
change_nick = '303' in status_codes
kick = '307' in status_codes and typ == 'unavailable'
@@ -836,9 +842,9 @@ class MucTab(ChatTab):
hide_exit_join = config.get('hide_exit_join', -1)
if hide_exit_join != 0:
if not jid.full:
- room.add_message('\x194%(spec)s \x193%(nick)s\x195 joined the room' % {'nick':from_nick, 'spec':theme.CHAR_JOIN})
+ room.add_message('\x194}%(spec)s \x193}%(nick)s\x195} joined the room' % {'nick':from_nick, 'spec':get_theme().CHAR_JOIN})
else:
- room.add_message('\x194%(spec)s \x193%(nick)s \x195(\x194%(jid)s\x195) joined the room' % {'spec':theme.CHAR_JOIN, 'nick':from_nick, 'jid':jid.full})
+ room.add_message('\x194}%(spec)s \x193}%(nick)s \x195}(\x194}%(jid)s\x195}) joined the room' % {'spec':get_theme().CHAR_JOIN, 'nick':from_nick, 'jid':jid.full})
self.core.on_user_rejoined_private_conversation(room.name, from_nick)
@@ -851,7 +857,7 @@ class MucTab(ChatTab):
if isinstance(_tab, PrivateTab) and JID(_tab.get_name()).bare == room.name:
_tab.get_room().own_nick = new_nick
user.change_nick(new_nick)
- room.add_message('\x193%(old)s\x195 is now known as \x193%(new)s' % {'old':from_nick, 'new':new_nick})
+ room.add_message('\x193}%(old)s\x195} is now known as \x193}%(new)s' % {'old':from_nick, 'new':new_nick})
# rename the private tabs if needed
self.core.rename_private_tabs(room.name, from_nick, new_nick)
@@ -869,16 +875,16 @@ class MucTab(ChatTab):
self.tab_win.refresh()
self.core.doupdate()
if by:
- kick_msg = _('\x191%(spec)s \x193You\x195 have been banned by \x194%(by)s') % {'spec': theme.CHAR_KICK, 'by':by}
+ kick_msg = _('\x191}%(spec)s \x193}You\x195} have been banned by \x194}%(by)s') % {'spec': get_theme().CHAR_KICK, 'by':by}
else:
- kick_msg = _('\x191%(spec)s \x193You\x195 have been banned.') % {'spec':theme.CHAR_KICK}
+ kick_msg = _('\x191}%(spec)s \x193}You\x195} have been banned.') % {'spec':get_theme().CHAR_KICK}
else:
if by:
- kick_msg = _('\x191%(spec)s \x193%(nick)s\x195 has been banned by \x194%(by)s') % {'spec':theme.CHAR_KICK, 'nick':from_nick, 'by':by}
+ kick_msg = _('\x191}%(spec)s \x193}%(nick)s\x195} has been banned by \x194}%(by)s') % {'spec':get_theme().CHAR_KICK, 'nick':from_nick, 'by':by}
else:
- kick_msg = _('\x191%(spec)s \x193%(nick)s\x195 has been banned') % {'spec':theme.CHAR_KICK, 'nick':from_nick.replace('"', '\\"')}
+ kick_msg = _('\x191}%(spec)s \x193}%(nick)s\x195 has been banned') % {'spec':get_theme().CHAR_KICK, 'nick':from_nick.replace('"', '\\"')}
if reason is not None and reason.text:
- kick_msg += _('\x195 Reason: \x196%(reason)s\x195') % {'reason': reason.text}
+ kick_msg += _('\x195} Reason: \x196}%(reason)s\x195}') % {'reason': reason.text}
room.add_message(kick_msg)
def on_user_kicked(self, room, presence, user, from_nick):
@@ -895,19 +901,19 @@ class MucTab(ChatTab):
self.tab_win.refresh()
self.core.doupdate()
if by:
- kick_msg = _('\x191%(spec)s \x193You\x195 have been kicked by \x193%(by)s') % {'spec': theme.CHAR_KICK, 'by':by}
+ kick_msg = _('\x191}%(spec)s \x193}You\x195} have been kicked by \x193}%(by)s') % {'spec': get_theme().CHAR_KICK, 'by':by}
else:
- kick_msg = _('\x191%(spec)s \x193You\x195 have been kicked.') % {'spec':theme.CHAR_KICK}
+ kick_msg = _('\x191}%(spec)s \x193}You\x195} have been kicked.') % {'spec':get_theme().CHAR_KICK}
# try to auto-rejoin
if config.get('autorejoin', 'false') == 'true':
muc.join_groupchat(self.core.xmpp, room.name, room.own_nick)
else:
if by:
- kick_msg = _('\x191%(spec)s \x193%(nick)s\x195 has been kicked by \x193%(by)s') % {'spec':theme.CHAR_KICK.replace('"', '\\"'), 'nick':from_nick.replace('"', '\\"'), 'by':by.replace('"', '\\"')}
+ kick_msg = _('\x191}%(spec)s \x193}%(nick)s\x195} has been kicked by \x193}%(by)s') % {'spec':get_theme().CHAR_KICK.replace('"', '\\"'), 'nick':from_nick.replace('"', '\\"'), 'by':by.replace('"', '\\"')}
else:
- kick_msg = _('\x191%(spec)s \x193%(nick)s\x195 has been kicked') % {'spec':theme.CHAR_KICK, 'nick':from_nick.replace('"', '\\"')}
+ kick_msg = _('\x191}%(spec)s \x193}%(nick)s\x195} has been kicked') % {'spec':get_theme().CHAR_KICK, 'nick':from_nick.replace('"', '\\"')}
if reason is not None and reason.text:
- kick_msg += _('\x195 Reason: \x196%(reason)s') % {'reason': reason.text}
+ kick_msg += _('\x195} Reason: \x196}%(reason)s') % {'reason': reason.text}
room.add_message(kick_msg)
def on_user_leave_groupchat(self, room, user, jid, status, from_nick, from_room):
@@ -924,9 +930,9 @@ class MucTab(ChatTab):
hide_exit_join = config.get('hide_exit_join', -1) if config.get('hide_exit_join', -1) >= -1 else -1
if hide_exit_join == -1 or user.has_talked_since(hide_exit_join):
if not jid.full:
- leave_msg = _('\x191%(spec)s \x193%(nick)s\x195 has left the room') % {'nick':from_nick, 'spec':theme.CHAR_QUIT}
+ leave_msg = _('\x191}%(spec)s \x193}%(nick)s\x195} has left the room') % {'nick':from_nick, 'spec':get_theme().CHAR_QUIT}
else:
- leave_msg = _('\x191%(spec)s \x193%(nick)s\x195 (\x194%(jid)s\x195) has left the room') % {'spec':theme.CHAR_QUIT, 'nick':from_nick, 'jid':jid.full}
+ leave_msg = _('\x191}%(spec)s \x193}%(nick)s\x195} (\x194}%(jid)s\x195}) has left the room') % {'spec':get_theme().CHAR_QUIT, 'nick':from_nick, 'jid':jid.full}
if status:
leave_msg += ' (%s)' % status
room.add_message(leave_msg)
@@ -940,7 +946,7 @@ class MucTab(ChatTab):
# build the message
display_message = False # flag to know if something significant enough
# to be displayed has changed
- msg = _('\x193%s\x195 changed: ')% from_nick.replace('"', '\\"')
+ msg = _('\x193}%s\x195} changed: ')% from_nick.replace('"', '\\"')
if show not in SHOW_NAME:
self.core.information("%s from room %s sent an invalid show: %s" %\
(from_nick, from_room, show), "warning")
@@ -993,12 +999,10 @@ class PrivateTab(ChatTab):
# keys
self.key_func['^I'] = self.completion
# commands
- self.commands['info'] = (self.command_info, _('Usage: /info\nInfo: Display some information about the user in the MUC: '), None)
+ #self.commands['info'] = (self.command_info, _('Usage: /info\nInfo: Display some information about the user in the MUC: '), None)
self.commands['unquery'] = (self.command_unquery, _("Usage: /unquery\nUnquery: close the tab"), None)
self.commands['part'] = (self.command_unquery, _("Usage: /part\nPart: close the tab"), None)
- self.commands['version'] = (self.command_version, _('Usage: /version\nVersion: get the software version of the current interlocutor (usually its XMPP client and Operating System)'), None)
self.resize()
- self.parent_muc = self.core.get_tab_by_name(JID(room.name).bare, MucTab)
self.on = True
def completion(self):
@@ -1029,35 +1033,9 @@ class PrivateTab(ChatTab):
"""
self.core.close_tab()
- def command_version(self, arg):
- """
- /version
- """
- def callback(res):
- if not res:
- return self.core.information('Could not get the software version from %s' % (jid,), 'Warning')
- version = '%s is running %s version %s on %s' % (jid,
- res.get('name') or _('an unknown software'),
- res.get('version') or _('unknown'),
- res.get('os') or _('on an unknown platform'))
- self.core.information(version, 'Info')
- jid = self.get_room().name
- self.core.xmpp.plugin['xep_0092'].get_version(jid, callback=callback)
-
- def command_info(self, arg):
- """
- /info
- """
- if arg:
- self.parent_muc.command_info(arg)
- else:
- user = JID(self.get_room().name).resource
- self.parent_muc.command_info(user)
-
def resize(self):
if self.core.information_win_size >= self.height-3 or not self.visible:
return
- self.need_resize = False
self.text_win.resize(self.height-3-self.core.information_win_size, self.width, 0, 0)
self.text_win.rebuild_everything(self._room)
self.info_header.resize(1, self.width, self.height-3-self.core.information_win_size, 0)
@@ -1078,10 +1056,10 @@ class PrivateTab(ChatTab):
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:
+ if self._room.color_state == get_theme().COLOR_TAB_NORMAL or\
+ self._room.color_state == get_theme().COLOR_TAB_CURRENT:
return self._room.color_state
- return theme.COLOR_TAB_PRIVATE
+ return get_theme().COLOR_TAB_PRIVATE
def set_color_state(self, color):
self._room.color_state = color
@@ -1103,14 +1081,14 @@ class PrivateTab(ChatTab):
return False
def on_lose_focus(self):
- self._room.set_color_state(theme.COLOR_TAB_NORMAL)
+ self._room.set_color_state(get_theme().COLOR_TAB_NORMAL)
self.text_win.remove_line_separator()
self.text_win.add_line_separator()
if self.get_room().joined and config.get('send_chat_states', 'true') == 'true' and not self.input.get_text():
self.send_chat_state('inactive')
def on_gain_focus(self):
- self._room.set_color_state(theme.COLOR_TAB_CURRENT)
+ self._room.set_color_state(get_theme().COLOR_TAB_CURRENT)
curses.curs_set(1)
if self.get_room().joined and config.get('send_chat_states', 'true') == 'true' and not self.input.get_text():
self.send_chat_state('active')
@@ -1146,24 +1124,22 @@ class PrivateTab(ChatTab):
"""
The user left the associated MUC
"""
- self.deactivate()
if not status_message:
- self.get_room().add_message(_('\x191%(spec)s \x193%(nick)s\x195 has left the room') % {'nick':from_nick.replace('"', '\\"'), 'spec':theme.CHAR_QUIT.replace('"', '\\"')})
+ self.get_room().add_message(_('\x191}%(spec)s \x193}%(nick)s\x195} has left the room') % {'nick':from_nick.replace('"', '\\"'), 'spec':get_theme().CHAR_QUIT.replace('"', '\\"')})
else:
- self.get_room().add_message(_('\x191%(spec)s \x193%(nick)s\x195 has left the room (%(status)s)"') % {'nick':from_nick.replace('"', '\\"'), 'spec':theme.CHAR_QUIT, 'status': status_message.replace('"', '\\"')})
- if self.core.current_tab() is self:
- self.refresh()
- self.core.doupdate()
+ self.get_room().add_message(_('\x191}%(spec)s \x193}%(nick)s\x195} has left the room (%(status)s)"') % {'nick':from_nick.replace('"', '\\"'), 'spec':get_theme().CHAR_QUIT, 'status': status_message.replace('"', '\\"')})
+ self.deactivate()
+ self.refresh()
+ self.core.doupdate()
def user_rejoined(self, nick):
"""
The user (or at least someone with the same nick) came back in the MUC
"""
+ self.get_room().add_message('\x194}%(spec)s \x193}%(nick)s\x195} joined the room' % {'nick':nick, 'spec':get_theme().CHAR_JOIN})
self.activate()
- self.get_room().add_message('\x194%(spec)s \x193%(nick)s\x195 joined the room' % {'nick':nick, 'spec':theme.CHAR_JOIN})
- if self.core.current_tab() is self:
- self.refresh()
- self.core.doupdate()
+ self.refresh()
+ self.core.doupdate()
def activate(self):
self.on = True
@@ -1185,7 +1161,7 @@ class RosterInfoTab(Tab):
self.contact_info_win = windows.ContactInfoWin()
self.default_help_message = windows.HelpText("Enter commands with “/”. “o”: toggle offline show")
self.input = self.default_help_message
- self.set_color_state(theme.COLOR_TAB_NORMAL)
+ self.set_color_state(get_theme().COLOR_TAB_NORMAL)
self.key_func['^I'] = self.completion
self.key_func[' '] = self.on_space
self.key_func["/"] = self.on_slash
@@ -1209,7 +1185,6 @@ class RosterInfoTab(Tab):
def resize(self):
if not self.visible:
return
- self.need_resize = False
roster_width = self.width//2
info_width = self.width-roster_width-1
self.v_separator.resize(self.height-2, 1, 0, roster_width)
@@ -1420,10 +1395,10 @@ class RosterInfoTab(Tab):
return self.reset_help_message()
def on_lose_focus(self):
- self._color_state = theme.COLOR_TAB_NORMAL
+ self._color_state = get_theme().COLOR_TAB_NORMAL
def on_gain_focus(self):
- self._color_state = theme.COLOR_TAB_CURRENT
+ self._color_state = get_theme().COLOR_TAB_CURRENT
if isinstance(self.input, windows.HelpText):
curses.curs_set(0)
else:
@@ -1526,7 +1501,7 @@ class ConversationTab(ChatTab):
def __init__(self, jid):
txt_buff = text_buffer.TextBuffer()
ChatTab.__init__(self, txt_buff)
- self.color_state = theme.COLOR_TAB_NORMAL
+ self.color_state = get_theme().COLOR_TAB_NORMAL
self._name = jid # a conversation tab is linked to one specific full jid OR bare jid
self.text_win = windows.TextWin()
txt_buff.add_window(self.text_win)
@@ -1566,7 +1541,6 @@ class ConversationTab(ChatTab):
def resize(self):
if self.core.information_win_size >= self.height-3 or not self.visible:
return
- self.need_resize = False
self.text_win.resize(self.height-4-self.core.information_win_size, self.width, 1, 0)
self.text_win.rebuild_everything(self._room)
self.upper_bar.resize(1, self.width, 0, 0)
@@ -1589,10 +1563,10 @@ class ConversationTab(ChatTab):
self.input.refresh()
def get_color_state(self):
- if self.color_state == theme.COLOR_TAB_NORMAL or\
- self.color_state == theme.COLOR_TAB_CURRENT:
+ if self.color_state == get_theme().COLOR_TAB_NORMAL or\
+ self.color_state == get_theme().COLOR_TAB_CURRENT:
return self.color_state
- return theme.COLOR_TAB_PRIVATE
+ return get_theme().COLOR_TAB_PRIVATE
def set_color_state(self, color):
self.color_state = color
@@ -1610,14 +1584,14 @@ class ConversationTab(ChatTab):
return False
def on_lose_focus(self):
- self.set_color_state(theme.COLOR_TAB_NORMAL)
+ self.set_color_state(get_theme().COLOR_TAB_NORMAL)
self.text_win.remove_line_separator()
self.text_win.add_line_separator()
if config.get('send_chat_states', 'true') == 'true' and not self.input.get_text() or not self.input.get_text().startswith('//'):
self.send_chat_state('inactive')
def on_gain_focus(self):
- self.set_color_state(theme.COLOR_TAB_CURRENT)
+ self.set_color_state(get_theme().COLOR_TAB_CURRENT)
curses.curs_set(1)
if config.get('send_chat_states', 'true') == 'true' and not self.input.get_text() or not self.input.get_text().startswith('//'):
self.send_chat_state('active')
@@ -1652,7 +1626,7 @@ class MucListTab(Tab):
"""
def __init__(self, server):
Tab.__init__(self)
- self._color_state = theme.COLOR_TAB_NORMAL
+ self._color_state = get_theme().COLOR_TAB_NORMAL
self.name = server
self.upper_message = windows.Topic()
self.upper_message.set_message('Chatroom list on server %s (Loading)' % self.name)
@@ -1684,7 +1658,6 @@ class MucListTab(Tab):
def resize(self):
if not self.visible:
return
- self.need_resize = False
self.upper_message.resize(1, self.width, 0, 0)
column_size = {'node-part': (self.width-5)//4,
'name': (self.width-5)//4*3,
@@ -1766,10 +1739,10 @@ class MucListTab(Tab):
return self.key_func[key]()
def on_lose_focus(self):
- self._color_state = theme.COLOR_TAB_NORMAL
+ self._color_state = get_theme().COLOR_TAB_NORMAL
def on_gain_focus(self):
- self._color_state = theme.COLOR_TAB_CURRENT
+ self._color_state = get_theme().COLOR_TAB_CURRENT
curses.curs_set(0)
def get_color_state(self):
@@ -1789,7 +1762,7 @@ class SimpleTextTab(Tab):
"""
def __init__(self, text):
Tab.__init__(self)
- self._color_state = theme.COLOR_TAB_NORMAL
+ self._color_state = get_theme().COLOR_TAB_NORMAL
self.text_win = windows.SimpleTextWin(text)
self.default_help_message = windows.HelpText("“Ctrl+q”: close")
self.input = self.default_help_message
@@ -1819,7 +1792,6 @@ class SimpleTextTab(Tab):
def resize(self):
if not self.visible:
return
- self.need_resize = False
self.text_win.resize(self.height-2, self.width, 0, 0)
self.input.resize(1, self.width, self.height-1, 0)
@@ -1832,10 +1804,10 @@ class SimpleTextTab(Tab):
self.input.refresh()
def on_lose_focus(self):
- self._color_state = theme.COLOR_TAB_NORMAL
+ self._color_state = get_theme().COLOR_TAB_NORMAL
def on_gain_focus(self):
- self._color_state = theme.COLOR_TAB_CURRENT
+ self._color_state = get_theme().COLOR_TAB_CURRENT
curses.curs_set(0)
def get_color_state(self):
diff --git a/src/text_buffer.py b/src/text_buffer.py
index d89dbfb9..f39f147a 100644
--- a/src/text_buffer.py
+++ b/src/text_buffer.py
@@ -15,7 +15,6 @@ log = logging.getLogger(__name__)
import collections
from datetime import datetime
-import theme
from config import config
Message = collections.namedtuple('Message', 'txt nick_color time str_time nickname user')
diff --git a/src/theme.py b/src/theme.py
deleted file mode 100644
index 2502e5c6..00000000
--- a/src/theme.py
+++ /dev/null
@@ -1,173 +0,0 @@
-# Copyright 2010-2011 Florent Le Coz <louiz@louiz.org>
-#
-# This file is part of Poezio.
-#
-# Poezio is free software: you can redistribute it and/or modify
-# it under the terms of the zlib license. See the COPYING file.
-
-"""
-Define the variables (colors and some other stuff) that are
-used when drawing the interface (mainly colors)
-"""
-
-import curses
-import glob
-import imp
-import os
-from config import config
-
-import logging
-log = logging.getLogger(__name__)
-
-## Define the default colors
-## Do not change these colors, create a theme file instead.
-
-# Message text color
-COLOR_NORMAL_TEXT = 0
-COLOR_INFORMATION_TEXT = 5
-COLOR_HIGHLIGHT_NICK = -46
-
-# User list color
-COLOR_USER_VISITOR = 7
-COLOR_USER_PARTICIPANT = 4
-COLOR_USER_NONE = 0
-COLOR_USER_MODERATOR = 1
-
-# nickname colors
-COLOR_REMOTE_USER = 5
-
-# The character printed in color (COLOR_STATUS_*) before the nickname
-# in the user list
-CHAR_STATUS = ' '
-
-# Separators
-COLOR_VERTICAL_SEPARATOR = 4
-COLOR_NEW_TEXT_SEPARATOR = 2
-COLOR_MORE_INDICATOR = 6
-
-# Time
-COLOR_TIME_SEPARATOR = 6
-COLOR_TIME_LIMITER = 0
-CHAR_TIME_LEFT = ''
-CHAR_TIME_RIGHT = ''
-COLOR_TIME_NUMBERS = 0
-
-# Tabs
-COLOR_TAB_NORMAL = 42
-COLOR_TAB_CURRENT = 56
-COLOR_TAB_NEW_MESSAGE = 49
-COLOR_TAB_HIGHLIGHT = 21
-COLOR_TAB_PRIVATE = 28
-COLOR_TAB_DISCONNECTED = 30
-
-# Nickname colors
-LIST_COLOR_NICKNAMES = [
- 1, 2, 3, 4, 5, 6, -2, -4, -5, -6
- ]
-COLOR_OWN_NICK = 7
-
-# Status color
-COLOR_STATUS_XA = 49
-COLOR_STATUS_NONE = 0
-COLOR_STATUS_DND = 21
-COLOR_STATUS_AWAY = 35
-COLOR_STATUS_CHAT = 28
-COLOR_STATUS_UNAVAILABLE = 57
-COLOR_STATUS_ONLINE = 41
-
-# Bars
-COLOR_INFORMATION_BAR = 42
-COLOR_TOPIC_BAR = 42
-COLOR_PRIVATE_ROOM_BAR = 28
-COLOR_SCROLLABLE_NUMBER = -39
-COLOR_SELECTED_ROW = 42
-COLOR_PRIVATE_NAME = 42
-COLOR_CONVERSATION_NAME = 42
-COLOR_GROUPCHAT_NAME = 42
-COLOR_COLUMN_HEADER = 36
-
-# Strings for special messages (like join, quit, nick change, etc)
-# Special messages
-CHAR_JOIN = '---->'
-CHAR_QUIT = '<----'
-CHAR_KICK = '-!-'
-
-COLOR_JOIN_CHAR = 4
-COLOR_QUIT_CHAR = 1
-COLOR_KICK_CHAR = 1
-
-# words between ()
-COLOR_CURLYBRACKETED_WORD = 4
-# words between {}
-COLOR_ACCOLADE_WORD = 6
-# words between []
-COLOR_BRACKETED_WORD = 3
-
-def init_colors():
- """
- Initilization of all the available ncurses colors
- limit the number of colors to 64 (because some terminals
- don't handle more than that), by removing some useless colors
- like 'black on black', etc.
- """
- curses.start_color()
- curses.use_default_colors()
- cpt = 0
- for i in range(-1, 7):
- for y in range(0, 8):
- if y == i:
- continue
- curses.init_pair(cpt, y, i)
- cpt += 1
- for y in range(0, 7):
- # init the default fg on others bg at last
- curses.init_pair(cpt, -1, y)
- cpt += 1
- # Have the default color be default fg on default bg
- reload_theme()
-
-def reload_theme():
- themes_dir = config.get('themes_dir', '')
- themes_dir = themes_dir or\
- os.path.join(os.environ.get('XDG_DATA_HOME') or\
- os.path.join(os.environ.get('HOME'), '.local', 'share'),
- 'poezio', 'themes')
- try:
- os.makedirs(themes_dir)
- except OSError:
- pass
- theme_name = config.get('theme', '')
- if not theme_name:
- return
- try:
- theme = imp.load_source('theme', os.path.join(themes_dir, theme_name))
- except: # TODO warning: theme not found
- return
- for var in dir(theme):
- if var.startswith('COLOR_') or var.startswith('CHAR_') or var.startswith('LIST_'):
- globals()[var] = getattr(theme, var)
-
-if __name__ == '__main__':
- """
- Launch 'python theme.py' to see the list of all the available colors
- in your terminal
- """
- s = curses.initscr()
- curses.start_color()
- curses.use_default_colors()
- init_colors()
- for i in range(64):
- s.attron(curses.color_pair(i) | curses.A_BOLD)
- s.addstr(str(curses.color_pair(i) | curses.A_BOLD))
- s.attroff(curses.color_pair(i) | curses.A_BOLD)
- s.addstr(' ')
- s.addstr('\n')
- for i in range(64):
- s.attron(curses.color_pair(i))
- s.addstr(str(i))
- s.attroff(curses.color_pair(i))
- s.addstr(' ')
-
- s.refresh()
- s.getch()
- s.endwin()
diff --git a/src/theming.py b/src/theming.py
new file mode 100644
index 00000000..382a3146
--- /dev/null
+++ b/src/theming.py
@@ -0,0 +1,265 @@
+# Copyright 2010-2011 Florent Le Coz <louiz@louiz.org>
+#
+# This file is part of Poezio.
+#
+# Poezio is free software: you can redistribute it and/or modify
+# it under the terms of the zlib license. See the COPYING file.
+
+"""
+Define the variables (colors and some other stuff) that are
+used when drawing the interface.
+
+Colors are numbers from -1 to 7 (if only 8 colors are supported) or -1 to 255
+if 256 colors are available.
+We check the number of available colors at startup, and we load a theme accordingly.
+A 8 color theme should NEVER use colors not in the -1 -> 7 range. We won't check that
+at run time. If the case occurs, the THEME should be fixed.
+XHTML-IM colors are converted to -1 -> 255 colors if available, or directly to
+-1 -> 8 if we are in 8-color-mode.
+
+A pair_color is a background-foreground pair. All possible pairs are not created
+at startup, because that would create 256*256 pairs, and almost all of them
+would never be used.
+So, a theme should define color tuples, like (200, -1), and when they are to
+be used by poezio's interface, they will be created once, and kept in a list for
+later usage.
+A color tuple is of the form (foreground, background, optional)
+A color of -1 means the default color. So if you do not want to have
+a background color, use (x, -1).
+The optional third value of the tuple defines additional information. It
+is a string and can contain one or more of these characteres:
+'b': bold
+'u': underlined
+'x': blink
+
+For example, (200, 208, 'bu') is bold, underlined and pink foreground on orange background.
+
+A theme file is a python file containing one object named 'theme', which is an
+instance of a class (derived from the Theme class) defined in that same file.
+For example, in pinkytheme.py:
+
+import theming
+class PinkyTheme(theming.Theme):
+ COLOR_NORMAL_TEXT = (200, -1)
+
+theme = PinkyTheme()
+
+if the command '/theme pinkytheme' is issued, we import the pinkytheme.py file
+and set the global variable 'theme' to pinkytheme.theme.
+
+And in poezio's code we just use theme.COLOR_NORMAL_TEXT etc
+
+Since a theme inherites from the Theme class (defined here), if a color is not defined in a
+theme file, the color is the default one.
+
+Some values in that class are a list of color tuple.
+For example [(1, -1), (2, -1), (3, -1)]
+Such a list SHOULD contain at list one color tuple.
+It is used for example to define color gradient, etc.
+"""
+
+import logging
+log = logging.getLogger(__name__)
+
+from config import config
+
+import curses
+import imp
+import os
+
+class Theme(object):
+ """
+ The theme class, from which all theme should inherit.
+ All of the following value can be replaced in subclasses, in
+ order to create a new theme.
+ Do not edit this file if you want to change the theme to suit your
+ needs. Create a new theme and share it if you think it can be useful
+ for others.
+ """
+ # Message text color
+ COLOR_NORMAL_TEXT = (-1, -1)
+ COLOR_INFORMATION_TEXT = (137, -1) # TODO
+ COLOR_HIGHLIGHT_NICK = (3, 5, 'b')
+
+ # User list color
+ COLOR_USER_VISITOR = (0, -1)
+ COLOR_USER_PARTICIPANT = (4, -1)
+ COLOR_USER_NONE = (0, -1)
+ COLOR_USER_MODERATOR = (1, -1)
+
+ # nickname colors
+ COLOR_REMOTE_USER = (5, -1)
+
+ # The character printed in color (COLOR_STATUS_*) before the nickname
+ # in the user list
+ CHAR_STATUS = '|'
+
+ # Separators
+ COLOR_VERTICAL_SEPARATOR = (4, -1)
+ COLOR_NEW_TEXT_SEPARATOR = (2, -1)
+ COLOR_MORE_INDICATOR = (6, 4)
+
+ # Time
+ COLOR_TIME_SEPARATOR = (106, -1)
+ COLOR_TIME_LIMITER = (0, -1)
+ CHAR_TIME_LEFT = ''
+ CHAR_TIME_RIGHT = ''
+ COLOR_TIME_NUMBERS = (0, -1)
+
+ # Tabs
+ COLOR_TAB_NORMAL = (7, 4)
+ COLOR_TAB_CURRENT = (7, 6)
+ COLOR_TAB_NEW_MESSAGE = (7, 5)
+ COLOR_TAB_HIGHLIGHT = (7, 1)
+ COLOR_TAB_PRIVATE = (7, 2)
+ COLOR_TAB_DISCONNECTED = (7, 8)
+
+ # Nickname colors
+ # A list of colors randomly attributed to nicks in MUCs
+ # Setting more colors makes it harder to have two nicks with the same color,
+ # avoiding confusions.
+ LIST_COLOR_NICKNAMES = [(1, -1), (2, -1), (3, -1), (4, -1), (5, -1), (6, -1), (7, -1), (8, -1), (9, -1), (10, -1), (11, -1), (12, -1), (13, -1), (14, -1), (23, -1), (23, -1), (88, -1), (99, -1), (100, -1), (154, -1), (213, -1), (216, -1), (227, -1)]
+ # This is your own nickname
+ COLOR_OWN_NICK = (254, -1)
+
+ # Status color
+ COLOR_STATUS_XA = (16, 90)
+ COLOR_STATUS_NONE = (16, 4)
+ COLOR_STATUS_DND = (16, 1)
+ COLOR_STATUS_AWAY = (16, 3)
+ COLOR_STATUS_CHAT = (16, 2)
+ COLOR_STATUS_UNAVAILABLE = (-1, 247)
+ COLOR_STATUS_ONLINE = (16, 4)
+
+ # Bars
+ COLOR_INFORMATION_BAR = (7, 4)
+ COLOR_TOPIC_BAR = (7, 4)
+ COLOR_SCROLLABLE_NUMBER = (220, 4, 'b')
+ COLOR_SELECTED_ROW = (-1, 33)
+ COLOR_PRIVATE_NAME = (-1, 4)
+ COLOR_CONVERSATION_NAME = (2, 4)
+ COLOR_GROUPCHAT_NAME = (7, 4)
+ COLOR_COLUMN_HEADER = (36, 4)
+
+ # Strings for special messages (like join, quit, nick change, etc)
+ # Special messages
+ CHAR_JOIN = '--->'
+ CHAR_QUIT = '<---'
+ CHAR_KICK = '-!-'
+
+ COLOR_JOIN_CHAR = (4, -1)
+ COLOR_QUIT_CHAR = (1, -1)
+ COLOR_KICK_CHAR = (1, -1)
+
+# This is the default theme object, used if no theme is defined in the conf
+theme = Theme()
+
+# a dict "color tuple -> color_pair"
+# Each time we use a color tuple, we check if it has already been used.
+# If not we create a new color_pair and keep it in that dict, to use it
+# the next time.
+curses_colors_dict = {}
+
+table_256_to_16 = [
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
+ 0, 4, 4, 4, 12, 12, 2, 6, 4, 4, 12, 12, 2, 2, 6, 4,
+ 12, 12, 2, 2, 2, 6, 12, 12, 10, 10, 10, 10, 14, 12, 10, 10,
+ 10, 10, 10, 14, 1, 5, 4, 4, 12, 12, 3, 8, 4, 4, 12, 12,
+ 2, 2, 6, 4, 12, 12, 2, 2, 2, 6, 12, 12, 10, 10, 10, 10,
+ 14, 12, 10, 10, 10, 10, 10, 14, 1, 1, 5, 4, 12, 12, 1, 1,
+ 5, 4, 12, 12, 3, 3, 8, 4, 12, 12, 2, 2, 2, 6, 12, 12,
+ 10, 10, 10, 10, 14, 12, 10, 10, 10, 10, 10, 14, 1, 1, 1, 5,
+ 12, 12, 1, 1, 1, 5, 12, 12, 1, 1, 1, 5, 12, 12, 3, 3,
+ 3, 7, 12, 12, 10, 10, 10, 10, 14, 12, 10, 10, 10, 10, 10, 14,
+ 9, 9, 9, 9, 13, 12, 9, 9, 9, 9, 13, 12, 9, 9, 9, 9,
+ 13, 12, 9, 9, 9, 9, 13, 12, 11, 11, 11, 11, 7, 12, 10, 10,
+ 10, 10, 10, 14, 9, 9, 9, 9, 9, 13, 9, 9, 9, 9, 9, 13,
+ 9, 9, 9, 9, 9, 13, 9, 9, 9, 9, 9, 13, 9, 9, 9, 9,
+ 9, 13, 11, 11, 11, 11, 11, 15, 0, 0, 0, 0, 0, 0, 8, 8,
+ 8, 8, 8, 8, 7, 7, 7, 7, 7, 7, 15, 15, 15, 15, 15, 15
+]
+
+def color_256_to_16(color):
+ if color == -1:
+ return color
+ return table_256_to_16[color]
+
+def to_curses_attr(color_tuple):
+ """
+ Takes a color tuple (as defined at the top of this file) and
+ returns a valid curses attr that can be passed directly to attron() or attroff()
+ """
+ # extract the color from that tuple
+ if len(color_tuple) == 3:
+ colors = (color_tuple[0], color_tuple[1])
+ else:
+ colors = color_tuple
+
+ bold = False
+ if curses.COLORS != 256:
+ # We are not in a term supporting 256 colors, so we convert
+ # colors to numbers between -1 and 8
+ colors = (color_256_to_16(colors[0]), color_256_to_16(colors[1]))
+ if colors[0] >= 8:
+ colors = (colors[0] - 8, colors[1])
+ bold = True
+ if colors[1] >= 8:
+ colors = (colors[0], colors[1] - 8)
+
+ # check if we already used these colors
+ try:
+ pair = curses_colors_dict[colors]
+ except KeyError:
+ pair = len(curses_colors_dict) + 1
+ curses.init_pair(pair, colors[0], colors[1])
+ curses_colors_dict[colors] = pair
+ curses_pair = curses.color_pair(pair)
+ if len(color_tuple) == 3:
+ additional_val = color_tuple[2]
+ if 'b' in additional_val or bold is True:
+ curses_pair = curses_pair | curses.A_BOLD
+ if 'u' in additional_val:
+ curses_pair = curses_pair | curses.A_UNDERLINE
+ if 'a' in additional_val:
+ curses_pair = curses_pair | curses.A_BLINK
+ return curses_pair
+
+def get_theme():
+ """
+ Returns the current theme
+ """
+ return theme
+
+def reload_theme():
+ themes_dir = config.get('themes_dir', '')
+ themes_dir = themes_dir or\
+ os.path.join(os.environ.get('XDG_DATA_HOME') or\
+ os.path.join(os.environ.get('HOME'), '.local', 'share'),
+ 'poezio', 'themes')
+ try:
+ os.makedirs(themes_dir)
+ except OSError:
+ pass
+ theme_name = config.get('theme', '')
+ if not theme_name:
+ return
+ try:
+ file_path = os.path.join(themes_dir, theme_name)+'.py'
+ log.debug('Theme file to load: %s' %(file_path,))
+ new_theme = imp.load_source('theme', os.path.join(themes_dir, theme_name)+'.py')
+ except: # TODO warning: theme not found
+ return
+ global theme
+ theme = new_theme.theme
+
+if __name__ == '__main__':
+ """
+ Display some nice text with nice colors
+ """
+ s = curses.initscr()
+ curses.start_color()
+ curses.use_default_colors()
+ s.addstr('%s' % curses.COLORS, to_curses_attr((3, -1, 'a')))
+ s.refresh()
+ s.getkey()
+ curses.endwin()
diff --git a/src/user.py b/src/user.py
index 0fe0bad4..5867e1f3 100644
--- a/src/user.py
+++ b/src/user.py
@@ -10,11 +10,13 @@ Define the user class.
An user is a MUC participant, not a roster contact (see contact.py)
"""
+import curses
+
from random import randrange, choice
from config import config
from datetime import timedelta, datetime
-import curses
-import theme
+
+from theming import get_theme
ROLE_DICT = {
'':0,
@@ -32,7 +34,7 @@ class User(object):
self.last_talked = datetime(1, 1, 1) # The oldest possible time
self.update(affiliation, show, status, role)
self.change_nick(nick)
- self.color = choice(theme.LIST_COLOR_NICKNAMES)
+ self.color = choice(get_theme().LIST_COLOR_NICKNAMES)
self.jid = jid
self.chatstate = None
diff --git a/src/windows.py b/src/windows.py
index 3e607541..2352a82a 100644
--- a/src/windows.py
+++ b/src/windows.py
@@ -33,12 +33,14 @@ from poopt import cut_text
from sleekxmpp.xmlstream.stanzabase import JID
import core
-import theme
import common
import wcwidth
import singleton
import collections
+from theming import get_theme, to_curses_attr
+
+allowed_color_digits = ('0', '1', '2', '3', '4', '5', '6', '7')
# msg is a reference to the corresponding Message tuple. text_start and text_end are the position
# delimiting the text in this line.
# first is a bool telling if this is the first line of the message.
@@ -111,7 +113,7 @@ class Win(object):
attributes as they are in the string.
For example:
\x19bhello → hello in bold
- \xc1Bonj\xc2our → 'Bonj' in red and 'our' in green
+ \x191}Bonj\x192}our → 'Bonj' in red and 'our' in green
next_attr_char is the \x19 delimiter
attr_char is the char following it, it can be
one of 'u', 'b', 'c[0-9]'
@@ -119,6 +121,37 @@ class Win(object):
if y is not None and x is not None:
self.move(y, x)
next_attr_char = text.find('\x19')
+ while next_attr_char != -1 and text:
+ log.debug('Addstr_Colored: [%s]' % text.replace('\x19', '\\x19'))
+ if next_attr_char + 1 < len(text):
+ attr_char = text[next_attr_char+1].lower()
+ else:
+ attr_char = str()
+ if next_attr_char != 0:
+ self.addstr(text[:next_attr_char])
+ if attr_char == 'o':
+ self._win.attrset(0)
+ elif attr_char == 'u':
+ self._win.attron(curses.A_UNDERLINE)
+ elif attr_char == 'b':
+ self._win.attron(curses.A_BOLD)
+ if attr_char in string.digits and attr_char != '':
+ color_str = text[next_attr_char+1:text.find('}', next_attr_char)]
+ self._win.attron(to_curses_attr((int(color_str), -1)))
+ text = text[next_attr_char+len(color_str)+2:]
+ else:
+ text = text[next_attr_char+2:]
+ next_attr_char = text.find('\x19')
+ self.addstr(text)
+
+ def addstr_colored_lite(self, text, y=None, x=None):
+ """
+ Just like addstr_colored, but only handles colors with one digit.
+ \x193 is the 3rd color. We do not use any } char in this version
+ """
+ if y is not None and x is not None:
+ self.move(y, x)
+ next_attr_char = text.find('\x19')
while next_attr_char != -1:
if next_attr_char + 1 < len(text):
attr_char = text[next_attr_char+1].lower()
@@ -134,7 +167,7 @@ class Win(object):
elif attr_char == 'b':
self._win.attron(curses.A_BOLD)
elif attr_char in string.digits and attr_char != '':
- self._win.attron(common.curses_color_pair(int(attr_char)))
+ self._win.attron(to_curses_attr((int(attr_char), -1)))
next_attr_char = text.find('\x19')
self.addstr(text)
@@ -145,7 +178,7 @@ class Win(object):
(y, x) = self._win.getyx()
size = self.width-x
if color:
- self.addnstr(' '*size, size, common.curses_color_pair(color))
+ self.addnstr(' '*size, size, to_curses_attr(color))
else:
self.addnstr(' '*size, size)
@@ -159,18 +192,18 @@ class UserList(Win):
def __init__(self):
Win.__init__(self)
self.pos = 0
- self.color_role = {'moderator': theme.COLOR_USER_MODERATOR,
- 'participant':theme.COLOR_USER_PARTICIPANT,
- 'visitor':theme.COLOR_USER_VISITOR,
- 'none':theme.COLOR_USER_NONE,
- '':theme.COLOR_USER_NONE
+ self.color_role = {'moderator': get_theme().COLOR_USER_MODERATOR,
+ 'participant':get_theme().COLOR_USER_PARTICIPANT,
+ 'visitor':get_theme().COLOR_USER_VISITOR,
+ 'none':get_theme().COLOR_USER_NONE,
+ '':get_theme().COLOR_USER_NONE
}
- self.color_show = {'xa':theme.COLOR_STATUS_XA,
- 'none':theme.COLOR_STATUS_NONE,
- '':theme.COLOR_STATUS_NONE,
- 'dnd':theme.COLOR_STATUS_DND,
- 'away':theme.COLOR_STATUS_AWAY,
- 'chat':theme.COLOR_STATUS_CHAT
+ self.color_show = {'xa':get_theme().COLOR_STATUS_XA,
+ 'none':get_theme().COLOR_STATUS_NONE,
+ '':get_theme().COLOR_STATUS_NONE,
+ 'dnd':get_theme().COLOR_STATUS_DND,
+ 'away':get_theme().COLOR_STATUS_AWAY,
+ 'chat':get_theme().COLOR_STATUS_CHAT
}
def scroll_up(self):
@@ -182,7 +215,7 @@ class UserList(Win):
self.pos = 0
def draw_plus(self, y):
- self.addstr(y, self.width-2, '++', common.curses_color_pair(theme.COLOR_MORE_INDICATOR))
+ self.addstr(y, self.width-2, '++', to_curses_attr(get_theme().COLOR_MORE_INDICATOR))
def refresh(self, users):
log.debug('Refresh: %s'%self.__class__.__name__)
@@ -194,11 +227,11 @@ class UserList(Win):
self.pos = len(users)-1
for user in users[self.pos:]:
if not user.role in self.color_role:
- role_col = theme.COLOR_USER_NONE
+ role_col = get_theme().COLOR_USER_NONE
else:
role_col = self.color_role[user.role]
if not user.show in self.color_show:
- show_col = theme.COLOR_STATUS_NONE
+ show_col = get_theme().COLOR_STATUS_NONE
else:
show_col = self.color_show[user.show]
if user.chatstate == 'composing':
@@ -208,9 +241,9 @@ class UserList(Win):
elif user.chatstate == 'paused':
char = 'p'
else:
- char = theme.CHAR_STATUS
- self.addstr(y, 0, char, common.curses_color_pair(show_col))
- self.addstr(y, 1, user.nick[:self.width-2], common.curses_color_pair(role_col))
+ char = get_theme().CHAR_STATUS
+ self.addstr(y, 0, char, to_curses_attr(show_col))
+ self.addstr(y, 1, user.nick[:self.width-2], to_curses_attr(role_col))
y += 1
if y == self.height:
break
@@ -223,9 +256,9 @@ class UserList(Win):
def resize(self, height, width, y, x):
self._resize(height, width, y, x)
- self._win.attron(common.curses_color_pair(theme.COLOR_VERTICAL_SEPARATOR))
+ self._win.attron(to_curses_attr(get_theme().COLOR_VERTICAL_SEPARATOR))
self._win.vline(0, 0, curses.ACS_VLINE, self.height)
- self._win.attroff(common.curses_color_pair(theme.COLOR_VERTICAL_SEPARATOR))
+ self._win.attroff(to_curses_attr(get_theme().COLOR_VERTICAL_SEPARATOR))
class Topic(Win):
def __init__(self):
@@ -240,12 +273,12 @@ class Topic(Win):
msg = topic[:self.width-1]
else:
msg = self._message[:self.width-1]
- self.addstr(0, 0, msg, common.curses_color_pair(theme.COLOR_TOPIC_BAR))
+ self.addstr(0, 0, msg, to_curses_attr(get_theme().COLOR_TOPIC_BAR))
(y, x) = self._win.getyx()
remaining_size = self.width - x
if remaining_size:
self.addnstr(' '*remaining_size, remaining_size,
- common.curses_color_pair(theme.COLOR_INFORMATION_BAR))
+ to_curses_attr(get_theme().COLOR_INFORMATION_BAR))
self._refresh()
def set_message(self, message):
@@ -262,24 +295,24 @@ class GlobalInfoBar(Win):
comp = lambda x: x.nb
with g_lock:
self._win.erase()
- self.addstr(0, 0, "[", common.curses_color_pair(theme.COLOR_INFORMATION_BAR))
+ self.addstr(0, 0, "[", to_curses_attr(get_theme().COLOR_INFORMATION_BAR))
sorted_tabs = sorted(self.core.tabs, key=comp)
for tab in sorted_tabs:
color = tab.get_color_state()
if config.get('show_inactive_tabs', 'true') == 'false' and\
- color == theme.COLOR_TAB_NORMAL:
+ color == get_theme().COLOR_TAB_NORMAL:
continue
try:
- self.addstr("%s" % str(tab.nb), common.curses_color_pair(color))
- self.addstr("|", common.curses_color_pair(theme.COLOR_INFORMATION_BAR))
+ self.addstr("%s" % str(tab.nb), to_curses_attr(color))
+ self.addstr("|", to_curses_attr(get_theme().COLOR_INFORMATION_BAR))
except: # end of line
break
(y, x) = self._win.getyx()
- self.addstr(y, x-1, '] ', common.curses_color_pair(theme.COLOR_INFORMATION_BAR))
+ self.addstr(y, x-1, '] ', to_curses_attr(get_theme().COLOR_INFORMATION_BAR))
(y, x) = self._win.getyx()
remaining_size = self.width - x
self.addnstr(' '*remaining_size, remaining_size,
- common.curses_color_pair(theme.COLOR_INFORMATION_BAR))
+ to_curses_attr(get_theme().COLOR_INFORMATION_BAR))
self._refresh()
class InfoWin(Win):
@@ -298,7 +331,7 @@ class InfoWin(Win):
"""
if window.pos > 0:
plus = ' -PLUS(%s)-' % window.pos
- self.addstr(plus, common.curses_color_pair(theme.COLOR_SCROLLABLE_NUMBER))
+ self.addstr(plus, to_curses_attr(get_theme().COLOR_SCROLLABLE_NUMBER))
class PrivateInfoWin(InfoWin):
"""
@@ -315,33 +348,33 @@ class PrivateInfoWin(InfoWin):
self.write_room_name(room)
self.print_scroll_position(window)
self.write_chatstate(chatstate)
- self.finish_line(theme.COLOR_INFORMATION_BAR)
+ self.finish_line(get_theme().COLOR_INFORMATION_BAR)
self._refresh()
def write_room_name(self, room):
jid = JID(room.name)
room_name, nick = jid.bare, jid.resource
- self.addstr(nick, common.curses_color_pair(theme.COLOR_PRIVATE_NAME))
+ self.addstr(nick, to_curses_attr(get_theme().COLOR_PRIVATE_NAME))
txt = ' from room %s' % room_name
- self.addstr(txt, common.curses_color_pair(theme.COLOR_INFORMATION_BAR))
+ self.addstr(txt, to_curses_attr(get_theme().COLOR_INFORMATION_BAR))
def write_chatstate(self, state):
if state:
- self.addstr(' %s' % (state,), common.curses_color_pair(theme.COLOR_INFORMATION_BAR))
+ self.addstr(' %s' % (state,), to_curses_attr(get_theme().COLOR_INFORMATION_BAR))
class ConversationInfoWin(InfoWin):
"""
The line above the information window, displaying informations
about the user we are talking to
"""
- color_show = {'xa':theme.COLOR_STATUS_XA,
- 'none':theme.COLOR_STATUS_ONLINE,
- '':theme.COLOR_STATUS_ONLINE,
- 'available':theme.COLOR_STATUS_ONLINE,
- 'dnd':theme.COLOR_STATUS_DND,
- 'away':theme.COLOR_STATUS_AWAY,
- 'chat':theme.COLOR_STATUS_CHAT,
- 'unavailable':theme.COLOR_STATUS_UNAVAILABLE
+ color_show = {'xa':get_theme().COLOR_STATUS_XA,
+ 'none':get_theme().COLOR_STATUS_ONLINE,
+ '':get_theme().COLOR_STATUS_ONLINE,
+ 'available':get_theme().COLOR_STATUS_ONLINE,
+ 'dnd':get_theme().COLOR_STATUS_DND,
+ 'away':get_theme().COLOR_STATUS_AWAY,
+ 'chat':get_theme().COLOR_STATUS_CHAT,
+ 'unavailable':get_theme().COLOR_STATUS_UNAVAILABLE
}
def __init__(self):
@@ -372,7 +405,7 @@ class ConversationInfoWin(InfoWin):
self.write_resource_information(resource)
self.print_scroll_position(window)
self.write_chatstate(chatstate)
- self.finish_line(theme.COLOR_INFORMATION_BAR)
+ self.finish_line(get_theme().COLOR_INFORMATION_BAR)
self._refresh()
def write_resource_information(self, resource):
@@ -384,31 +417,31 @@ class ConversationInfoWin(InfoWin):
else:
presence = resource.get_presence()
color = RosterWin.color_show[presence]
- self.addstr('[', common.curses_color_pair(theme.COLOR_INFORMATION_BAR))
- self.addstr(" ", common.curses_color_pair(color))
- self.addstr(']', common.curses_color_pair(theme.COLOR_INFORMATION_BAR))
+ self.addstr('[', to_curses_attr(get_theme().COLOR_INFORMATION_BAR))
+ self.addstr(" ", to_curses_attr(color))
+ self.addstr(']', to_curses_attr(get_theme().COLOR_INFORMATION_BAR))
def write_contact_informations(self, contact):
"""
Write the informations about the contact
"""
if not contact:
- self.addstr("(contact not in roster)", common.curses_color_pair(theme.COLOR_INFORMATION_BAR))
+ self.addstr("(contact not in roster)", to_curses_attr(get_theme().COLOR_INFORMATION_BAR))
return
display_name = contact.get_name() or contact.get_bare_jid()
- self.addstr('%s '%(display_name), common.curses_color_pair(theme.COLOR_INFORMATION_BAR))
+ self.addstr('%s '%(display_name), to_curses_attr(get_theme().COLOR_INFORMATION_BAR))
def write_contact_jid(self, jid):
"""
Just write the jid that we are talking to
"""
- self.addstr('[', common.curses_color_pair(theme.COLOR_INFORMATION_BAR))
- self.addstr(jid.full, common.curses_color_pair(theme.COLOR_CONVERSATION_NAME))
- self.addstr('] ', common.curses_color_pair(theme.COLOR_INFORMATION_BAR))
+ self.addstr('[', to_curses_attr(get_theme().COLOR_INFORMATION_BAR))
+ self.addstr(jid.full, to_curses_attr(get_theme().COLOR_CONVERSATION_NAME))
+ self.addstr('] ', to_curses_attr(get_theme().COLOR_INFORMATION_BAR))
def write_chatstate(self, state):
if state:
- self.addstr(' %s' % (state,), common.curses_color_pair(theme.COLOR_INFORMATION_BAR))
+ self.addstr(' %s' % (state,), to_curses_attr(get_theme().COLOR_INFORMATION_BAR))
class ConversationStatusMessageWin(InfoWin):
"""
@@ -431,11 +464,11 @@ class ConversationStatusMessageWin(InfoWin):
self._win.erase()
if resource:
self.write_status_message(resource)
- self.finish_line(theme.COLOR_INFORMATION_BAR)
+ self.finish_line(get_theme().COLOR_INFORMATION_BAR)
self._refresh()
def write_status_message(self, resource):
- self.addstr(resource.get_status(), common.curses_color_pair(theme.COLOR_INFORMATION_BAR))
+ self.addstr(resource.get_status(), to_curses_attr(get_theme().COLOR_INFORMATION_BAR))
class MucInfoWin(InfoWin):
"""
@@ -455,20 +488,20 @@ class MucInfoWin(InfoWin):
self.write_role(room)
if window:
self.print_scroll_position(window)
- self.finish_line(theme.COLOR_INFORMATION_BAR)
+ self.finish_line(get_theme().COLOR_INFORMATION_BAR)
self._refresh()
def write_room_name(self, room):
- self.addstr('[', common.curses_color_pair(theme.COLOR_INFORMATION_BAR))
- self.addstr(room.name, common.curses_color_pair(theme.COLOR_GROUPCHAT_NAME))
- self.addstr('] ', common.curses_color_pair(theme.COLOR_INFORMATION_BAR))
+ self.addstr('[', to_curses_attr(get_theme().COLOR_INFORMATION_BAR))
+ self.addstr(room.name, to_curses_attr(get_theme().COLOR_GROUPCHAT_NAME))
+ self.addstr('] ', to_curses_attr(get_theme().COLOR_INFORMATION_BAR))
def write_disconnected(self, room):
"""
Shows a message if the room is not joined
"""
if not room.joined:
- self.addstr(' -!- Not connected ', common.curses_color_pair(theme.COLOR_INFORMATION_BAR))
+ self.addstr(' -!- Not connected ', to_curses_attr(get_theme().COLOR_INFORMATION_BAR))
def write_own_nick(self, room):
"""
@@ -477,7 +510,7 @@ class MucInfoWin(InfoWin):
nick = room.own_nick
if not nick:
return
- self.addstr(truncate_nick(nick, 13), common.curses_color_pair(theme.COLOR_INFORMATION_BAR))
+ self.addstr(truncate_nick(nick, 13), to_curses_attr(get_theme().COLOR_INFORMATION_BAR))
def write_role(self, room):
"""
@@ -494,7 +527,7 @@ class MucInfoWin(InfoWin):
if own_user.affiliation != 'none':
txt += own_user.affiliation+', '
txt += own_user.role+')'
- self.addstr(txt, common.curses_color_pair(theme.COLOR_INFORMATION_BAR))
+ self.addstr(txt, to_curses_attr(get_theme().COLOR_INFORMATION_BAR))
class TextWin(Win):
def __init__(self, lines_nb_limit=config.get('max_lines_in_memory', 2048)):
@@ -559,9 +592,9 @@ class TextWin(Win):
offset = 1 + len(message.str_time)
if nick:
offset += wcwidth.wcswidth(nick) + 2 # + nick + spaces length
- if theme.CHAR_TIME_LEFT:
+ if get_theme().CHAR_TIME_LEFT:
offset += 1
- if theme.CHAR_TIME_RIGHT:
+ if get_theme().CHAR_TIME_RIGHT:
offset += 1
lines = cut_text(txt, self.width-offset)
for line in lines:
@@ -610,7 +643,7 @@ class TextWin(Win):
self._refresh()
def write_line_separator(self):
- self.addnstr('- '*(self.width//2-1)+'-', self.width, common.curses_color_pair(theme.COLOR_NEW_TEXT_SEPARATOR))
+ self.addnstr('- '*(self.width//2-1)+'-', self.width, to_curses_attr(get_theme().COLOR_NEW_TEXT_SEPARATOR))
def write_text(self, y, x, txt):
"""
@@ -626,10 +659,10 @@ class TextWin(Win):
if not nickname:
return
if color:
- self._win.attron(common.curses_color_pair(color))
+ self._win.attron(to_curses_attr(color))
self.addstr(truncate_nick(nickname))
if color:
- self._win.attroff(common.curses_color_pair(color))
+ self._win.attroff(to_curses_attr(color))
self.addstr("> ")
def write_time(self, time):
@@ -672,8 +705,8 @@ class HelpText(Win):
self.txt = txt
with g_lock:
self._win.erase()
- self.addstr(0, 0, self.txt[:self.width-1], common.curses_color_pair(theme.COLOR_INFORMATION_BAR))
- self.finish_line(theme.COLOR_INFORMATION_BAR)
+ self.addstr(0, 0, self.txt[:self.width-1], to_curses_attr(get_theme().COLOR_INFORMATION_BAR))
+ self.finish_line(get_theme().COLOR_INFORMATION_BAR)
self._refresh()
def do_command(self, key):
@@ -1034,16 +1067,16 @@ class Input(Win):
text = self.text.replace('\n', '|')
self._win.erase()
if self.color:
- self._win.attron(common.curses_color_pair(self.color))
+ self._win.attron(to_curses_attr(self.color))
displayed_text = text[self.line_pos:self.line_pos+self.width-1]
self.addstr(displayed_text)
if self.color:
(y, x) = self._win.getyx()
size = self.width-x
- self.addnstr(' '*size, size, common.curses_color_pair(self.color))
+ self.addnstr(' '*size, size, to_curses_attr(self.color))
self.addstr(0, wcwidth.wcswidth(displayed_text[:self.pos]), '')
if self.color:
- self._win.attroff(common.curses_color_pair(self.color))
+ self._win.attroff(to_curses_attr(self.color))
self._refresh()
def refresh(self):
@@ -1100,7 +1133,7 @@ class MessageInput(Input):
Read one more char (c) and add \x19c to the string
"""
attr_char = self.core.read_keyboard()[0]
- if attr_char in self.text_attributes or (attr_char in string.digits and int(attr_char) < 7):
+ if attr_char in self.text_attributes or attr_char in allowed_color_digits:
self.do_command('\x19', False)
self.do_command(attr_char)
@@ -1138,13 +1171,13 @@ class MessageInput(Input):
text = self.text.replace('\n', '|')
self._win.erase()
if self.color:
- self._win.attron(common.curses_color_pair(self.color))
+ self._win.attron(to_curses_attr(self.color))
displayed_text = text[self.line_pos:self.line_pos+self.width-1]
self._win.attrset(0)
- self.addstr_colored(displayed_text)
+ self.addstr_colored_lite(displayed_text)
self.addstr(0, wcwidth.wcswidth(displayed_text[:self.pos]), '')
if self.color:
- self._win.attroff(common.curses_color_pair(self.color))
+ self._win.attroff(to_curses_attr(self.color))
self._refresh()
class CommandInput(Input):
@@ -1201,7 +1234,7 @@ class CommandInput(Input):
"""
with g_lock:
self._win.erase()
- self.addstr(self.help_message, common.curses_color_pair(theme.COLOR_INFORMATION_BAR))
+ self.addstr(self.help_message, to_curses_attr(get_theme().COLOR_INFORMATION_BAR))
cursor_pos = self.pos + len(self.help_message)
if len(self.help_message):
self.addstr(' ')
@@ -1276,7 +1309,7 @@ class VerticalSeparator(Win):
def rewrite_line(self):
with g_lock:
- self._win.vline(0, 0, curses.ACS_VLINE, self.height, common.curses_color_pair(theme.COLOR_VERTICAL_SEPARATOR))
+ self._win.vline(0, 0, curses.ACS_VLINE, self.height, to_curses_attr(get_theme().COLOR_VERTICAL_SEPARATOR))
self._refresh()
def refresh(self):
@@ -1284,14 +1317,14 @@ class VerticalSeparator(Win):
self.rewrite_line()
class RosterWin(Win):
- color_show = {'xa':theme.COLOR_STATUS_XA,
- 'none':theme.COLOR_STATUS_ONLINE,
- '':theme.COLOR_STATUS_ONLINE,
- 'available':theme.COLOR_STATUS_ONLINE,
- 'dnd':theme.COLOR_STATUS_DND,
- 'away':theme.COLOR_STATUS_AWAY,
- 'chat':theme.COLOR_STATUS_CHAT,
- 'unavailable':theme.COLOR_STATUS_UNAVAILABLE
+ color_show = {'xa':get_theme().COLOR_STATUS_XA,
+ 'none':get_theme().COLOR_STATUS_ONLINE,
+ '':get_theme().COLOR_STATUS_ONLINE,
+ 'available':get_theme().COLOR_STATUS_ONLINE,
+ 'dnd':get_theme().COLOR_STATUS_DND,
+ 'away':get_theme().COLOR_STATUS_AWAY,
+ 'chat':get_theme().COLOR_STATUS_CHAT,
+ 'unavailable':get_theme().COLOR_STATUS_UNAVAILABLE
}
def __init__(self):
@@ -1388,22 +1421,22 @@ class RosterWin(Win):
Draw the indicator that shows that
the list is longer than what is displayed
"""
- self.addstr(y, self.width-5, '++++', common.curses_color_pair(theme.COLOR_MORE_INDICATOR))
+ self.addstr(y, self.width-5, '++++', to_curses_attr(get_theme().COLOR_MORE_INDICATOR))
def draw_roster_information(self, roster):
"""
The header at the top
"""
self.addstr('Roster: %s/%s contacts' % (roster.get_nb_connected_contacts(), roster.get_contact_len())\
- , common.curses_color_pair(theme.COLOR_INFORMATION_BAR))
- self.finish_line(theme.COLOR_INFORMATION_BAR)
+ , to_curses_attr(get_theme().COLOR_INFORMATION_BAR))
+ self.finish_line(get_theme().COLOR_INFORMATION_BAR)
def draw_group(self, y, group, colored):
"""
Draw a groupname on a line
"""
if colored:
- self._win.attron(common.curses_color_pair(theme.COLOR_SELECTED_ROW))
+ self._win.attron(to_curses_attr(get_theme().COLOR_SELECTED_ROW))
if group.folded:
self.addstr(y, 0, '[+] ')
else:
@@ -1411,7 +1444,7 @@ class RosterWin(Win):
contacts = " (%s/%s)" % (group.get_nb_connected_contacts(), len(group))
self.addstr(y, 4, group.name + contacts)
if colored:
- self._win.attroff(common.curses_color_pair(theme.COLOR_SELECTED_ROW))
+ self._win.attroff(to_curses_attr(get_theme().COLOR_SELECTED_ROW))
self.finish_line()
def draw_contact_line(self, y, contact, colored):
@@ -1438,16 +1471,16 @@ class RosterWin(Win):
else:
display_name = '%s%s' % (contact.get_bare_jid(), nb,)
self.addstr(y, 0, ' ')
- self.addstr(" ", common.curses_color_pair(color))
+ self.addstr(" ", to_curses_attr(color))
if resource:
self.addstr(' [+]' if contact._folded else ' [-]')
self.addstr(' ')
if colored:
- self.addstr(display_name, common.curses_color_pair(theme.COLOR_SELECTED_ROW))
+ self.addstr(display_name, to_curses_attr(get_theme().COLOR_SELECTED_ROW))
else:
self.addstr(display_name)
if contact.get_ask() == 'asked':
- self.addstr('?', common.curses_color_pair(theme.COLOR_HIGHLIGHT_NICK))
+ self.addstr('?', to_curses_attr(get_theme().COLOR_HIGHLIGHT_NICK))
self.finish_line()
def draw_resource_line(self, y, resource, colored):
@@ -1455,9 +1488,9 @@ class RosterWin(Win):
Draw a specific resource line
"""
color = RosterWin.color_show[resource.get_presence()]
- self.addstr(y, 4, " ", common.curses_color_pair(color))
+ self.addstr(y, 4, " ", to_curses_attr(color))
if colored:
- self.addstr(y, 6, resource.get_jid().full, common.curses_color_pair(theme.COLOR_SELECTED_ROW))
+ self.addstr(y, 6, resource.get_jid().full, to_curses_attr(get_theme().COLOR_SELECTED_ROW))
else:
self.addstr(y, 6, resource.get_jid().full)
self.finish_line()
@@ -1482,12 +1515,12 @@ class ContactInfoWin(Win):
presence = resource.get_presence()
else:
presence = 'unavailable'
- self.addstr(0, 0, '%s (%s)'%(jid, presence,), common.curses_color_pair(theme.COLOR_INFORMATION_BAR))
- self.finish_line(theme.COLOR_INFORMATION_BAR)
+ self.addstr(0, 0, '%s (%s)'%(jid, presence,), to_curses_attr(get_theme().COLOR_INFORMATION_BAR))
+ self.finish_line(get_theme().COLOR_INFORMATION_BAR)
self.addstr(1, 0, 'Subscription: %s' % (contact.get_subscription(),))
if contact.get_ask():
if contact.get_ask() == 'asked':
- self.addstr(' Ask: %s' % (contact.get_ask(),), common.curses_color_pair(theme.COLOR_HIGHLIGHT_NICK))
+ self.addstr(' Ask: %s' % (contact.get_ask(),), to_curses_attr(get_theme().COLOR_HIGHLIGHT_NICK))
else:
self.addstr(' Ask: %s' % (contact.get_ask(),))
self.finish_line()
@@ -1497,8 +1530,8 @@ class ContactInfoWin(Win):
"""
draw the group information
"""
- self.addstr(0, 0, group.name, common.curses_color_pair(theme.COLOR_INFORMATION_BAR))
- self.finish_line(theme.COLOR_INFORMATION_BAR)
+ self.addstr(0, 0, group.name, to_curses_attr(get_theme().COLOR_INFORMATION_BAR))
+ self.finish_line(get_theme().COLOR_INFORMATION_BAR)
def refresh(self, selected_row):
log.debug('Refresh: %s'%self.__class__.__name__)
@@ -1581,7 +1614,7 @@ class ListWin(Win):
if not txt:
continue
if line is self.lines[self._selected_row]:
- self.addstr(y, x, txt[:size], common.curses_color_pair(theme.COLOR_INFORMATION_BAR))
+ self.addstr(y, x, txt[:size], to_curses_attr(get_theme().COLOR_INFORMATION_BAR))
else:
self.addstr(y, x, txt[:size])
x += size
@@ -1659,7 +1692,7 @@ class ColumnHeaderWin(Win):
txt = col
size = self._columns_sizes[col]
txt += ' ' * (size-len(txt))
- self.addstr(0, x, txt, common.curses_color_pair(theme.COLOR_COLUMN_HEADER))
+ self.addstr(0, x, txt, to_curses_attr(get_theme().COLOR_COLUMN_HEADER))
x += size
self._refresh()
diff --git a/src/xhtml.py b/src/xhtml.py
index 8f629e3b..9bb2705d 100644
--- a/src/xhtml.py
+++ b/src/xhtml.py
@@ -22,12 +22,159 @@ from config import config
import logging
digits = '0123456789' # never trust the modules
+colors = {
+ 'aliceblue': 231,
+ 'antiquewhite': 231,
+ 'aqua': 51,
+ 'aquamarine': 122,
+ 'azure': 231,
+ 'beige': 231,
+ 'bisque': 230,
+ 'black': 232,
+ 'blanchedalmond': 230,
+ 'blue': 21,
+ 'blueviolet': 135,
+ 'brown': 124,
+ 'burlywood': 223,
+ 'cadetblue': 109,
+ 'chartreuse': 118,
+ 'chocolate': 172,
+ 'coral': 209,
+ 'cornflowerblue': 111,
+ 'cornsilk': 231,
+ 'crimson': 197,
+ 'cyan': 51,
+ 'darkblue': 19,
+ 'darkcyan': 37,
+ 'darkgoldenrod': 178,
+ 'darkgray': 247,
+ 'darkgreen': 28,
+ 'darkgrey': 247,
+ 'darkkhaki': 186,
+ 'darkmagenta': 127,
+ 'darkolivegreen': 65,
+ 'darkorange': 214,
+ 'darkorchid': 134,
+ 'darkred': 124,
+ 'darksalmon': 216,
+ 'darkseagreen': 151,
+ 'darkslateblue': 61,
+ 'darkslategray': 59,
+ 'darkslategrey': 59,
+ 'darkturquoise': 44,
+ 'darkviolet': 128,
+ 'deeppink': 199,
+ 'deepskyblue': 45,
+ 'dimgray': 241,
+ 'dimgrey': 241,
+ 'dodgerblue': 39,
+ 'firebrick': 160,
+ 'floralwhite': 231,
+ 'forestgreen': 34,
+ 'fuchsia': 201,
+ 'gainsboro': 252,
+ 'ghostwhite': 231,
+ 'gold': 226,
+ 'goldenrod': 214,
+ 'gray': 244,
+ 'green': 34,
+ 'greenyellow': 191,
+ 'grey': 244,
+ 'honeydew': 231,
+ 'hotpink': 212,
+ 'indianred': 174,
+ 'indigo': 55,
+ 'ivory': 231,
+ 'khaki': 229,
+ 'lavender': 231,
+ 'lavenderblush': 231,
+ 'lawngreen': 118,
+ 'lemonchiffon': 230,
+ 'lightblue': 195,
+ 'lightcoral': 217,
+ 'lightcyan': 231,
+ 'lightgoldenrodyellow': 230,
+ 'lightgray': 251,
+ 'lightgreen': 157,
+ 'lightgrey': 251,
+ 'lightpink': 224,
+ 'lightsalmon': 216,
+ 'lightseagreen': 43,
+ 'lightskyblue': 153,
+ 'lightslategray': 109,
+ 'lightslategrey': 109,
+ 'lightsteelblue': 189,
+ 'lightyellow': 231,
+ 'lime': 46,
+ 'limegreen': 77,
+ 'linen': 231,
+ 'magenta': 201,
+ 'maroon': 124,
+ 'mediumaquamarine': 115,
+ 'mediumblue': 20,
+ 'mediumorchid': 170,
+ 'mediumpurple': 141,
+ 'mediumseagreen': 78,
+ 'mediumslateblue': 105,
+ 'mediumspringgreen': 49,
+ 'mediumturquoise': 80,
+ 'mediumvioletred': 163,
+ 'midnightblue': 18,
+ 'mintcream': 231,
+ 'mistyrose': 231,
+ 'moccasin': 230,
+ 'navajowhite': 230,
+ 'navy': 19,
+ 'oldlace': 231,
+ 'olive': 142,
+ 'olivedrab': 106,
+ 'orange': 214,
+ 'orangered': 202,
+ 'orchid': 213,
+ 'palegoldenrod': 229,
+ 'palegreen': 157,
+ 'paleturquoise': 195,
+ 'palevioletred': 211,
+ 'papayawhip': 231,
+ 'peachpuff': 230,
+ 'peru': 179,
+ 'pink': 224,
+ 'plum': 219,
+ 'powderblue': 195,
+ 'purple': 127,
+ 'red': 196,
+ 'rosybrown': 181,
+ 'royalblue': 69,
+ 'saddlebrown': 130,
+ 'salmon': 216,
+ 'sandybrown': 216,
+ 'seagreen': 72,
+ 'seashell': 231,
+ 'sienna': 131,
+ 'silver': 250,
+ 'skyblue': 153,
+ 'slateblue': 104,
+ 'slategray': 109,
+ 'slategrey': 109,
+ 'snow': 231,
+ 'springgreen': 48,
+ 'steelblue': 74,
+ 'tan': 187,
+ 'teal': 37,
+ 'thistle': 225,
+ 'tomato': 209,
+ 'turquoise': 86,
+ 'violet': 219,
+ 'wheat': 230,
+ 'white': 255,
+ 'whitesmoke': 255,
+ 'yellow': 226,
+ 'yellowgreen': 149
+}
log = logging.getLogger(__name__)
-shell_colors_re = re.compile(r'(\[(?:\d+;)*(?:\d+m))')
-start_indent_re = re.compile(r'\[0;30m\[0;37m ')
-newline_indent_re = re.compile('\n\[0;37m ')
+whitespace_re = re.compile(r'\s+')
def get_body_from_message_stanza(message):
"""
@@ -41,31 +188,38 @@ def get_body_from_message_stanza(message):
return xhtml_to_poezio_colors(xhtml_body)
return message['body']
-
def xhtml_to_poezio_colors(text):
def parse_css(css):
- def get_color(string):
- if value == 'black':
- return 0
- if value == 'red':
- return 1
- if value == 'green':
- return 2
- if value == 'yellow':
- return 3
- if value == 'blue':
- return 4
- if value == 'magenta':
- return 5
- if value == 'cyan':
- return 6
- if value == 'white':
- return 7
- if value == 'default':
- return 8
+ def get_color(value):
+ if value[0] == '#':
+ value = value[1:]
+ length = len(value)
+ if length != 3 and length != 6:
+ return -1
+ value = int(value, 16)
+ if length == 6:
+ r = int(value >> 16)
+ g = int((value >> 8) & 0xff)
+ b = int(value & 0xff)
+ if r == g == b:
+ return 232 + int(r/10.6251)
+ div = 42.51
+ else:
+ r = int(value >> 8)
+ g = int((value >> 4) & 0xf)
+ b = int(value & 0xf)
+ if r == g == b:
+ return 232 + int(1.54*r)
+ div = 2.51
+ return 6*6*int(r/div) + 6*int(g/div) + int(b/div) + 16
+ if value in colors:
+ return colors[value]
+ return -1
shell = ''
rules = css.split(';')
for rule in rules:
+ if ':' not in rule:
+ continue
key, value = rule.split(':', 1)
key = key.strip()
value = value.strip()
@@ -73,7 +227,7 @@ def xhtml_to_poezio_colors(text):
if key == 'background-color':
pass#shell += '\x191'
elif key == 'color':
- shell += '\x19%d' % get_color(value)
+ shell += '\x19%d}' % get_color(value)
elif key == 'font-style':
shell += '\x19i'
elif key == 'font-weight':
@@ -89,13 +243,16 @@ def xhtml_to_poezio_colors(text):
shell += '\x19a'
return shell
+ def trim(string):
+ return re.sub(whitespace_re, ' ', string)
+
log.debug(text)
xml = ET.fromstring(text)
message = ''
for elem in xml.iter():
if elem.tag == '{http://www.w3.org/1999/xhtml}a':
if 'href' in elem.attrib and elem.attrib['href'] != elem.text:
- message += '\x19u%s\x19o (%s)' % (elem.attrib['href'], elem.text)
+ message += '\x19u%s\x19o (%s)' % (trim(elem.attrib['href']), trim(elem.text))
else:
message += '\x19u' + elem.text + '\x19o'
elif elem.tag == '{http://www.w3.org/1999/xhtml}blockquote':
@@ -109,8 +266,8 @@ def xhtml_to_poezio_colors(text):
elif elem.tag == '{http://www.w3.org/1999/xhtml}em':
message += '\x19i'
elif elem.tag == '{http://www.w3.org/1999/xhtml}img' and 'src' in elem.attrib:
- if elem.attrib['alt']:
- message += '%s (%s)' % (elem.attrib['src'], elem.attrib['alt'])
+ if 'alt' in elem.attrib:
+ message += '%s (%s)' % (trim(elem.attrib['src']), trim(elem.attrib['alt']))
else:
message += elem.attrib['src']
elif elem.tag == '{http://www.w3.org/1999/xhtml}li':
@@ -134,7 +291,7 @@ def xhtml_to_poezio_colors(text):
if (elem.text and elem.tag != '{http://www.w3.org/1999/xhtml}a'
and elem.tag != '{http://www.w3.org/1999/xhtml}br'
and elem.tag != '{http://www.w3.org/1999/xhtml}img'):
- message += elem.text
+ message += trim(elem.text)
if ('style' in elem.attrib and elem.tag != '{http://www.w3.org/1999/xhtml}br'
and elem.tag != '{http://www.w3.org/1999/xhtml}em'
@@ -156,7 +313,7 @@ def xhtml_to_poezio_colors(text):
message += ' [' + elem.attrib['title'] + ']'
if elem.tail:
- message += elem.tail
+ message += trim(elem.tail)
return message
@@ -223,7 +380,6 @@ def poezio_colors_to_html(string):
res += "</p></body>"
return res.replace('\n', '<br />')
-
def poezio_colors_to_xhtml(string):
"""
Generate a valid xhtml string from