Add conversion of WeeChat colors to Qt colors, add section "color" in config file

This commit is contained in:
Sebastien Helleu 2011-12-23 20:18:57 +01:00
parent c728febdd5
commit 3a5ec0c163
6 changed files with 277 additions and 30 deletions

View File

@ -98,7 +98,7 @@ class BufferWidget(QtGui.QWidget):
# splitter with chat + nicklist # splitter with chat + nicklist
self.chat_nicklist = QtGui.QSplitter() self.chat_nicklist = QtGui.QSplitter()
self.chat_nicklist.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) self.chat_nicklist.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding)
self.chat = ChatTextEdit() self.chat = ChatTextEdit(debug=False)
self.chat_nicklist.addWidget(self.chat) self.chat_nicklist.addWidget(self.chat)
self.nicklist = GenericListWidget() self.nicklist = GenericListWidget()
if not display_nicklist: if not display_nicklist:

View File

@ -27,37 +27,98 @@ import datetime
import qt_compat import qt_compat
QtCore = qt_compat.import_module('QtCore') QtCore = qt_compat.import_module('QtCore')
QtGui = qt_compat.import_module('QtGui') QtGui = qt_compat.import_module('QtGui')
import config
import weechat.color as color
class ChatTextEdit(QtGui.QTextEdit): class ChatTextEdit(QtGui.QTextEdit):
"""Chat area.""" """Chat area."""
def __init__(self, *args): def __init__(self, debug, *args):
apply(QtGui.QTextEdit.__init__, (self,) + args) apply(QtGui.QTextEdit.__init__, (self,) + args)
self.debug = debug
self.readOnly = True self.readOnly = True
self.setFocusPolicy(QtCore.Qt.NoFocus) self.setFocusPolicy(QtCore.Qt.NoFocus)
self.setFontFamily('monospace') self.setFontFamily('monospace')
self._textcolor = self.textColor()
self._bgcolor = QtGui.QColor('#FFFFFF')
self._setcolorcode = { 'F': (self.setTextColor, self._textcolor),
'B': (self.setTextBackgroundColor, self._bgcolor) }
self._setfont = { '*': self.setFontWeight,
'_': self.setFontUnderline,
'/': self.setFontItalic }
self._fontvalues = { False: { '*': QtGui.QFont.Normal, '_': False, '/': False },
True: { '*': QtGui.QFont.Bold, '_': True, '/': True } }
self._color = color.Color(config.color_options(), self.debug)
def display(self, time, prefix, text, color=None): def display(self, time, prefix, text, forcecolor=None):
oldcolor = self.textColor()
if time == 0: if time == 0:
d = datetime.datetime.now() d = datetime.datetime.now()
else: else:
d = datetime.datetime.fromtimestamp(float(time)) d = datetime.datetime.fromtimestamp(float(time))
self.setTextColor(QtGui.QColor('#999999')) self.setTextColor(QtGui.QColor('#999999'))
self.insertPlainText(d.strftime('%H:%M ')) self.insertPlainText(d.strftime('%H:%M '))
self.setTextColor(oldcolor) prefix = self._color.convert(prefix)
text = self._color.convert(text)
if forcecolor:
if prefix:
prefix = '\x01(F%s)%s' % (forcecolor, prefix)
text = '\x01(F%s)%s' % (forcecolor, text)
if prefix: if prefix:
self.insertPlainText(str(prefix).decode('utf-8') + ' ') self._display_with_colors(str(prefix).decode('utf-8') + ' ')
if color: if text:
self.setTextColor(QtGui.QColor(color)) self._display_with_colors(str(text).decode('utf-8'))
self.insertPlainText(str(text).decode('utf-8')) if text[-1:] != '\n':
if text[-1:] != '\n': self.insertPlainText('\n')
else:
self.insertPlainText('\n') self.insertPlainText('\n')
if color:
self.setTextColor(oldcolor)
self.scroll_bottom() self.scroll_bottom()
def _display_with_colors(self, string):
self.setTextColor(self._textcolor)
self.setTextBackgroundColor(self._bgcolor)
self._reset_attributes()
items = string.split('\x01')
for i, item in enumerate(items):
if i > 0 and item.startswith('('):
pos = item.find(')')
if pos >= 2:
action = item[1]
code = item[2:pos]
if action == '+':
# set attribute
self._set_attribute(code[0], True)
elif action == '-':
# remove attribute
self._set_attribute(code[0], False)
else:
# reset attributes and color
if code == 'r':
self._reset_attributes()
self._setcolorcode[action][0](self._setcolorcode[action][1])
else:
# set attributes + color
while code.startswith(('*', '!', '/', '_', '|', 'r')):
if code[0] == 'r':
self._reset_attributes()
elif code[0] in self._setfont:
self._set_attribute(code[0], not self._font[code[0]])
code = code[1:]
if code:
self._setcolorcode[action][0](QtGui.QColor(code))
item = item[pos+1:]
if len(item) > 0:
self.insertPlainText(item)
def _reset_attributes(self):
self._font = {}
for attr in self._setfont:
self._set_attribute(attr, False)
def _set_attribute(self, attr, value):
self._font[attr] = value
self._setfont[attr](self._fontvalues[self._font[attr]][attr])
def scroll_bottom(self): def scroll_bottom(self):
bar = self.verticalScrollBar() bar = self.verticalScrollBar()
bar.setValue(bar.maximum()) bar.setValue(bar.maximum())

View File

@ -24,11 +24,12 @@
# #
import os, ConfigParser import os, ConfigParser
import weechat.color as color
CONFIG_DIR = '%s/.qweechat' % os.getenv('HOME') CONFIG_DIR = '%s/.qweechat' % os.getenv('HOME')
CONFIG_FILENAME = '%s/qweechat.conf' % CONFIG_DIR CONFIG_FILENAME = '%s/qweechat.conf' % CONFIG_DIR
CONFIG_DEFAULT_SECTIONS = ('relay', 'look') CONFIG_DEFAULT_SECTIONS = ('relay', 'look', 'color')
CONFIG_DEFAULT_OPTIONS = (('relay.server', ''), CONFIG_DEFAULT_OPTIONS = (('relay.server', ''),
('relay.port', ''), ('relay.port', ''),
('relay.password', ''), ('relay.password', ''),
@ -36,9 +37,51 @@ CONFIG_DEFAULT_OPTIONS = (('relay.server', ''),
('look.debug', 'off'), ('look.debug', 'off'),
('look.statusbar', 'off')) ('look.statusbar', 'off'))
# Default colors for WeeChat color options (option name, #rgb value)
CONFIG_DEFAULT_COLOR_OPTIONS = (('separator', '#000066'), # 0
('chat', '#000000'), # 1
('chat_time', '#999999'), # 2
('chat_time_delimiters', '#000000'), # 3
('chat_prefix_error', '#FF6633'), # 4
('chat_prefix_network', '#990099'), # 5
('chat_prefix_action', '#000000'), # 6
('chat_prefix_join', '#00CC00'), # 7
('chat_prefix_quit', '#CC0000'), # 8
('chat_prefix_more', '#CC00FF'), # 9
('chat_prefix_suffix', '#330099'), # 10
('chat_buffer', '#000000'), # 11
('chat_server', '#000000'), # 12
('chat_channel', '#000000'), # 13
('chat_nick', '#000000'), # 14
('chat_nick_self', '*#000000'), # 15
('chat_nick_other', '#000000'), # 16
('', '#000000'), # 17 (nick1 -- obsolete)
('', '#000000'), # 18 (nick2 -- obsolete)
('', '#000000'), # 19 (nick3 -- obsolete)
('', '#000000'), # 20 (nick4 -- obsolete)
('', '#000000'), # 21 (nick5 -- obsolete)
('', '#000000'), # 22 (nick6 -- obsolete)
('', '#000000'), # 23 (nick7 -- obsolete)
('', '#000000'), # 24 (nick8 -- obsolete)
('', '#000000'), # 25 (nick9 -- obsolete)
('', '#000000'), # 26 (nick10 -- obsolete)
('chat_host', '#666666'), # 27
('chat_delimiters', '#9999FF'), # 28
('chat_highlight', '#3399CC'), # 29
('chat_read_marker', '#000000'), # 30
('chat_text_found', '#000000'), # 31
('chat_value', '#000000'), # 32
('chat_prefix_buffer', '#000000'), # 33
('chat_tags', '#000000'), # 34
('chat_inactive_window', '#000000'), # 35
('chat_inactive_buffer', '#000000'), # 36
('chat_prefix_buffer_inactive_buffer', '#000000')) # 37
config_color_options = []
def read(): def read():
"""Read config file.""" """Read config file."""
global config_color_options
config = ConfigParser.RawConfigParser() config = ConfigParser.RawConfigParser()
if os.path.isfile(CONFIG_FILENAME): if os.path.isfile(CONFIG_FILENAME):
config.read(CONFIG_FILENAME) config.read(CONFIG_FILENAME)
@ -51,6 +94,19 @@ def read():
section, name = option[0].split('.', 1) section, name = option[0].split('.', 1)
if not config.has_option(section, name): if not config.has_option(section, name):
config.set(section, name, option[1]) config.set(section, name, option[1])
section = 'color'
for option in reversed(CONFIG_DEFAULT_COLOR_OPTIONS):
if option[0] and not config.has_option(section, option[0]):
config.set(section, option[0], option[1])
# build list of color options
config_color_options = []
for option in CONFIG_DEFAULT_COLOR_OPTIONS:
if option[0]:
config_color_options.append(config.get('color', option[0]))
else:
config_color_options.append('#000000')
return config return config
def write(config): def write(config):
@ -59,3 +115,7 @@ def write(config):
os.mkdir(CONFIG_DIR, 0755) os.mkdir(CONFIG_DIR, 0755)
with open(CONFIG_FILENAME, 'wb') as cfg: with open(CONFIG_FILENAME, 'wb') as cfg:
config.write(cfg) config.write(cfg)
def color_options():
global config_color_options
return config_color_options

View File

@ -37,7 +37,7 @@ class DebugDialog(QtGui.QDialog):
self.resize(640, 480) self.resize(640, 480)
self.setWindowTitle('Debug console') self.setWindowTitle('Debug console')
self.chat = ChatTextEdit() self.chat = ChatTextEdit(debug=True)
self.input = InputLineEdit(self.chat) self.input = InputLineEdit(self.chat)
vbox = QtGui.QVBoxLayout() vbox = QtGui.QVBoxLayout()

View File

@ -35,7 +35,6 @@ QtCore = qt_compat.import_module('QtCore')
QtGui = qt_compat.import_module('QtGui') QtGui = qt_compat.import_module('QtGui')
import config import config
import weechat.protocol as protocol import weechat.protocol as protocol
import weechat.color as color
from network import Network from network import Network
from connection import ConnectionDialog from connection import ConnectionDialog
from buffer import BufferListWidget, Buffer from buffer import BufferListWidget, Buffer
@ -153,7 +152,7 @@ class MainWindow(QtGui.QMainWindow):
if self.network.is_connected(): if self.network.is_connected():
message = 'input %s %s\n' % (full_name, text) message = 'input %s %s\n' % (full_name, text)
self.network.send_to_weechat(message) self.network.send_to_weechat(message)
self.debug_display(0, '<==', message, color='red') self.debug_display(0, '<==', message, forcecolor='#AA0000')
def open_preferences_dialog(self): def open_preferences_dialog(self):
pass # TODO pass # TODO
@ -180,8 +179,8 @@ class MainWindow(QtGui.QMainWindow):
text = '(debug_%s)%s' % (text[1:pos], text[pos+1:]) text = '(debug_%s)%s' % (text[1:pos], text[pos+1:])
else: else:
text = '(debug) %s' % text text = '(debug) %s' % text
self.debug_display(0, '<==', text, forcecolor='#AA0000')
self.network.send_to_weechat(text + '\n') self.network.send_to_weechat(text + '\n')
self.debug_display(0, '<==', text, color='red')
def debug_dialog_closed(self, result): def debug_dialog_closed(self, result):
self.debug_dialog = None self.debug_dialog = None
@ -210,7 +209,7 @@ class MainWindow(QtGui.QMainWindow):
def network_status_changed(self, status, extra): def network_status_changed(self, status, extra):
if self.config.getboolean('look', 'statusbar'): if self.config.getboolean('look', 'statusbar'):
self.statusBar().showMessage(status) self.statusBar().showMessage(status)
self.debug_display(0, '', status, color='blue') self.debug_display(0, '', status, forcecolor='#0000AA')
self.network_status_set(status, extra) self.network_status_set(status, extra)
def network_status_set(self, status, extra): def network_status_set(self, status, extra):
@ -236,7 +235,7 @@ class MainWindow(QtGui.QMainWindow):
self.debug_display(0, '==>', self.debug_display(0, '==>',
'message (%d bytes):\n%s' 'message (%d bytes):\n%s'
% (len(message), protocol.hex_and_ascii(message, 20)), % (len(message), protocol.hex_and_ascii(message, 20)),
color='green') forcecolor='#008800')
proto = protocol.Protocol() proto = protocol.Protocol()
message = proto.decode(str(message)) message = proto.decode(str(message))
if message.uncompressed: if message.uncompressed:
@ -244,7 +243,7 @@ class MainWindow(QtGui.QMainWindow):
'message uncompressed (%d bytes):\n%s' 'message uncompressed (%d bytes):\n%s'
% (message.size_uncompressed, % (message.size_uncompressed,
protocol.hex_and_ascii(message.uncompressed, 20)), protocol.hex_and_ascii(message.uncompressed, 20)),
color='green') forcecolor='#008800')
self.debug_display(0, '', 'Message: %s' % message) self.debug_display(0, '', 'Message: %s' % message)
self.parse_message(message) self.parse_message(message)
@ -276,8 +275,8 @@ class MainWindow(QtGui.QMainWindow):
index = [i for i, b in enumerate(self.buffers) if b.pointer() == ptrbuf] index = [i for i, b in enumerate(self.buffers) if b.pointer() == ptrbuf]
if index: if index:
self.buffers[index[0]].widget.chat.display(item['date'], self.buffers[index[0]].widget.chat.display(item['date'],
color.remove(item['prefix']), item['prefix'],
color.remove(item['message'])) item['message'])
elif message.msgid in ('_nicklist', 'nicklist'): elif message.msgid in ('_nicklist', 'nicklist'):
buffer_nicklist = {} buffer_nicklist = {}
for obj in message.objects: for obj in message.objects:

View File

@ -29,15 +29,142 @@ RE_COLOR_ATTRS = r'[*!/_|]*'
RE_COLOR_STD = r'(?:%s\d{2})' % RE_COLOR_ATTRS RE_COLOR_STD = r'(?:%s\d{2})' % RE_COLOR_ATTRS
RE_COLOR_EXT = r'(?:@%s\d{5})' % RE_COLOR_ATTRS RE_COLOR_EXT = r'(?:@%s\d{5})' % RE_COLOR_ATTRS
RE_COLOR_ANY = r'(?:%s|%s)' % (RE_COLOR_STD, RE_COLOR_EXT) RE_COLOR_ANY = r'(?:%s|%s)' % (RE_COLOR_STD, RE_COLOR_EXT)
# \x19: color code, \x1A: set attribute, \x1B: remove attribute, \x1C: reset
RE_COLOR = re.compile(r'(\x19(?:\d{2}|F%s|B\d{2}|B@\d{5}|\\*%s(,%s)?|@\d{5}|b.|\x1C))|\x1A.|\x1B.|\x1C' RE_COLOR = re.compile(r'(\x19(?:\d{2}|F%s|B\d{2}|B@\d{5}|\\*%s(,%s)?|@\d{5}|b.|\x1C))|\x1A.|\x1B.|\x1C'
% (RE_COLOR_ANY, RE_COLOR_ANY, RE_COLOR_ANY)) % (RE_COLOR_ANY, RE_COLOR_ANY, RE_COLOR_ANY))
def _replace_color(match): TERMINAL_COLORS = \
return match.group(0) '000000cd000000cd00cdcd000000cdcd00cd00cdcde5e5e54d4d4dff000000ff00ffff000000ffff00ff00ffffffffff' \
'00000000002a0000550000800000aa0000d4002a00002a2a002a55002a80002aaa002ad400550000552a005555005580' \
'0055aa0055d400800000802a0080550080800080aa0080d400aa0000aa2a00aa5500aa8000aaaa00aad400d40000d42a' \
'00d45500d48000d4aa00d4d42a00002a002a2a00552a00802a00aa2a00d42a2a002a2a2a2a2a552a2a802a2aaa2a2ad4' \
'2a55002a552a2a55552a55802a55aa2a55d42a80002a802a2a80552a80802a80aa2a80d42aaa002aaa2a2aaa552aaa80' \
'2aaaaa2aaad42ad4002ad42a2ad4552ad4802ad4aa2ad4d455000055002a5500555500805500aa5500d4552a00552a2a' \
'552a55552a80552aaa552ad455550055552a5555555555805555aa5555d455800055802a5580555580805580aa5580d4' \
'55aa0055aa2a55aa5555aa8055aaaa55aad455d40055d42a55d45555d48055d4aa55d4d480000080002a800055800080' \
'8000aa8000d4802a00802a2a802a55802a80802aaa802ad480550080552a8055558055808055aa8055d480800080802a' \
'8080558080808080aa8080d480aa0080aa2a80aa5580aa8080aaaa80aad480d40080d42a80d45580d48080d4aa80d4d4' \
'aa0000aa002aaa0055aa0080aa00aaaa00d4aa2a00aa2a2aaa2a55aa2a80aa2aaaaa2ad4aa5500aa552aaa5555aa5580' \
'aa55aaaa55d4aa8000aa802aaa8055aa8080aa80aaaa80d4aaaa00aaaa2aaaaa55aaaa80aaaaaaaaaad4aad400aad42a' \
'aad455aad480aad4aaaad4d4d40000d4002ad40055d40080d400aad400d4d42a00d42a2ad42a55d42a80d42aaad42ad4' \
'd45500d4552ad45555d45580d455aad455d4d48000d4802ad48055d48080d480aad480d4d4aa00d4aa2ad4aa55d4aa80' \
'd4aaaad4aad4d4d400d4d42ad4d455d4d480d4d4aad4d4d40808081212121c1c1c2626263030303a3a3a4444444e4e4e' \
'5858586262626c6c6c7676768080808a8a8a9494949e9e9ea8a8a8b2b2b2bcbcbcc6c6c6d0d0d0dadadae4e4e4eeeeee'
def remove(text): # WeeChat basic colors (color name, index in terminal colors)
"""Remove colors in a WeeChat string.""" WEECHAT_BASIC_COLORS = (('default', 0), ('black', 0), ('darkgray', 8), ('red', 1),
if not text: ('lightred', 9), ('green', 2), ('lightgreen', 10), ('brown', 3),
return text ('yellow', 11), ('blue', 4), ('lightblue', 12), ('magenta', 5),
return re.sub(RE_COLOR, '', text) ('lightmagenta', 13), ('cyan', 6), ('lightcyan', 14), ('gray', 7),
#return RE_COLOR.sub(_replace_color, text) ('white', 0))
class Color():
def __init__(self, color_options, debug=False):
self.color_options = color_options
self.debug = debug
def _rgb_color(self, index):
color = TERMINAL_COLORS[index*6:(index*6)+6]
r = int(color[0:2], 16) * 0.85
g = int(color[2:4], 16) * 0.85
b = int(color[4:6], 16) * 0.85
return '%02x%02x%02x' % (r, g, b)
def _convert_weechat_color(self, color):
try:
index = int(color)
return '\x01(Fr%s)' % self.color_options[index]
except:
print 'Error decoding WeeChat color "%s"' % color
return ''
def _convert_terminal_color(self, fg_bg, attrs, color):
try:
index = int(color)
return '\x01(%s%s#%s)' % (fg_bg, attrs, self._rgb_color(index))
except:
print 'Error decoding terminal color "%s"' % color
return ''
def _convert_color_attr(self, fg_bg, color):
extended = False
if color[0].startswith('@'):
extended = True
color = color[1:]
attrs = ''
keep_attrs = False
while color.startswith(('*', '!', '/', '_', '|')):
if color[0] == '|':
keep_attrs = True
attrs += color[0]
color = color[1:]
if extended:
return self._convert_terminal_color(fg_bg, attrs, color)
try:
index = int(color)
return self._convert_terminal_color(fg_bg, attrs, WEECHAT_BASIC_COLORS[index][1])
except:
print 'Error decoding color "%s"' % color
return ''
def _attrcode_to_char(self, code):
codes = { '\x01': '*', '\x02': '!', '\x03': '/', '\x04': '_' }
return codes.get(code, '')
def _convert_color(self, match):
color = match.group(0)
if color[0] == '\x19':
if color[1] == 'b':
# bar code, ignored
return ''
elif color[1] == '\x1C':
# reset
return '\x01(Fr)\x01(Br)'
elif color[1] in ('F', 'B'):
# foreground or background
return self._convert_color_attr(color[1], color[2:])
elif color[1] == '*':
# foreground with optional background
items = color[2:].split(',')
s = self._convert_color_attr('F', items[0])
if len(items) > 1:
s += self._convert_color_attr('B', items[1])
return s
elif color[1] == '@':
# direct ncurses pair number, ignored
return ''
if color[1:].isdigit():
return self._convert_weechat_color(int(color[1:]))
# color code
pass
elif color[0] == '\x1A':
# set attribute
return '\x01(+%s)' % self._attrcode_to_char(color[1])
elif color[0] == '\x1B':
# remove attribute
return '\x01(-%s)' % self._attrcode_to_char(color[1])
elif color[0] == '\x1C':
# reset
return '\x01(Fr)\x01(Br)'
# should never be executed!
return match.group(0)
def _convert_color_debug(self, match):
group = match.group(0)
for code in (0x01, 0x02, 0x03, 0x04, 0x19, 0x1A, 0x1B):
group = group.replace(chr(code), '<x%02X>' % code)
return group
def convert(self, text):
if not text:
return ''
if self.debug:
return RE_COLOR.sub(self._convert_color_debug, text)
else:
return RE_COLOR.sub(self._convert_color, text)
def remove(self, text):
"""Remove colors in a WeeChat string."""
if not text:
return ''
return re.sub(RE_COLOR, '', text)