This commit is contained in:
Florent Le Coz 2011-09-11 04:22:04 +02:00
commit b7b1faebdb
8 changed files with 380 additions and 75 deletions

View file

@ -6,6 +6,8 @@ LOCALEDIR=$(DATADIR)/locale
MANDIR=$(DATADIR)/man MANDIR=$(DATADIR)/man
all: Makefile all: Makefile
python3 setup.py build
cp build/lib.linux-x86_64-3.2/poopt.cpython-32mu.so src/
clean: clean:
find ./ -name \*.pyc -delete find ./ -name \*.pyc -delete

16
setup.py Normal file
View file

@ -0,0 +1,16 @@
from distutils.core import setup, Extension
module_poopt = Extension('poopt',
sources = ['src/pooptmodule.c'])
setup (name = 'BuildLines',
version = '0.0.1',
description = 'Poezio Optimizations',
ext_modules = [module_poopt],
author = 'Florent Le Coz',
author_email = 'louiz@louiz.org',
long_description = """
a python3 module for poezio, used to replace some time-critical
python functions that are too slow. If compiled, poezio will use this module,
otherwise it will just use the equivalent python functions.
""")

View file

@ -355,7 +355,8 @@ class Core(object):
return return
log.debug('on_got_offline: %s' % presence) log.debug('on_got_offline: %s' % presence)
resource = contact.get_resource_by_fulljid(jid.full) resource = contact.get_resource_by_fulljid(jid.full)
assert resource if not resource:
return
# If a resource got offline, display the message in the conversation with this # If a resource got offline, display the message in the conversation with this
# precise resource. # 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 \x191offline' % (resource.get_jid().full))
@ -674,7 +675,7 @@ class Core(object):
roster.add_contact(contact, jid) roster.add_contact(contact, jid)
roster.edit_groups_of_contact(contact, []) roster.edit_groups_of_contact(contact, [])
contact.set_ask('asked') contact.set_ask('asked')
self.tabs[0].set_color_state(theme.COLOR_TAB_HIGHLIGHT) self.get_tab_by_number(0).set_color_state(theme.COLOR_TAB_HIGHLIGHT)
self.information('%s wants to subscribe to your presence'%jid, 'Roster') self.information('%s wants to subscribe to your presence'%jid, 'Roster')
if isinstance(self.current_tab(), tabs.RosterInfoTab): if isinstance(self.current_tab(), tabs.RosterInfoTab):
self.refresh_window() self.refresh_window()
@ -777,6 +778,12 @@ class Core(object):
return tab return tab
return None return None
def get_tab_by_number(self, number):
for tab in self.tabs:
if tab.nb == number:
return tab
return None
def get_room_by_name(self, name): def get_room_by_name(self, name):
""" """
returns the room that has this name returns the room that has this name

279
src/pooptmodule.c Normal file
View file

@ -0,0 +1,279 @@
/* 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 MIT license. See the COPYING file. */
/** The poopt python3 module
**/
/* This file is a python3 module for poezio, used to replace some time-critical
python functions that are too slow. If compiled, poezio will use this module,
otherwise it will just use the equivalent python functions. */
#define PY_SSIZE_T_CLEAN
#include "Python.h"
PyObject *ErrorObject;
/***
The module functions
***/
/* cut_text: takes a string and returns a tuple of int.
Each two int tuple is a line, represented by the ending position it (where it should be cut).
Not that this position is calculed using the position of the python string characters,
not just the individual bytes.
For example, poopt_cut_text("vivent les frigidaires", 6);
will return [(0, 6), (7, 10), (11, 17), (17, 22)], meaning that the lines are
"vivent", "les", "frigid" and "aires"
*/
PyDoc_STRVAR(poopt_cut_text_doc, "cut_text(width, text)\n\n\nReturn the list of strings, cut according to the given size.");
static PyObject *poopt_cut_text(PyObject *self, PyObject *args)
{
/* int length; */
unsigned char *buffer;
int width;
if (PyArg_ParseTuple(args, "si", &buffer, &width) == 0)
return NULL;
int bpos = 0; /* the real position in the char* */
int spos = 0; /* the position, considering UTF-8 chars */
int last_space = -1;
int start_pos = 0;
PyObject* retlist = PyList_New(0);
while (buffer[bpos])
{
if (buffer[bpos] == ' ')
last_space = spos;
else if (buffer[bpos] == '\n')
{
if (PyList_Append(retlist, Py_BuildValue("ii", start_pos, spos)) == -1)
return NULL;
start_pos = spos + 1;
last_space = -1;
}
else if ((spos - start_pos) >= width)
{
if (last_space == -1)
{
if (PyList_Append(retlist, Py_BuildValue("ii", start_pos, spos)) == -1)
return NULL;
start_pos = spos;
}
else
{
if (PyList_Append(retlist, Py_BuildValue("ii", start_pos, last_space)) == -1)
return NULL;
start_pos = last_space + 1;
last_space = -1;
}
}
if (buffer[bpos] == 25) /* \x19 */
{
spos++;
bpos += 2;
}
else
if (buffer[bpos] <= 127) /* ASCII char on one byte */
bpos += 1;
else if (buffer[bpos] >= 194 && buffer[bpos] <= 223)
bpos += 2;
else if (buffer[bpos] >= 224 && buffer[bpos] <= 239)
bpos += 3;
else if (buffer[bpos] >= 240 && buffer[bpos] <= 244)
bpos += 4;
else
return NULL;
spos++;
}
if (PyList_Append(retlist, Py_BuildValue("(i,i)", start_pos, spos)) == -1)
return NULL;
return retlist;
}
/***
Module initialization. Just taken from the xxmodule.c template from the python sources.
***/
static PyTypeObject Str_Type = {
PyVarObject_HEAD_INIT(NULL, 0)
"pooptmodule.Str", /*tp_name*/
0, /*tp_basicsize*/
0, /*tp_itemsize*/
/* methods */
0, /*tp_dealloc*/
0, /*tp_print*/
0, /*tp_getattr*/
0, /*tp_setattr*/
0, /*tp_reserved*/
0, /*tp_repr*/
0, /*tp_as_number*/
0, /*tp_as_sequence*/
0, /*tp_as_mapping*/
0, /*tp_hash*/
0, /*tp_call*/
0, /*tp_str*/
0, /*tp_getattro*/
0, /*tp_setattro*/
0, /*tp_as_buffer*/
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
0, /*tp_doc*/
0, /*tp_traverse*/
0, /*tp_clear*/
0, /*tp_richcompare*/
0, /*tp_weaklistoffset*/
0, /*tp_iter*/
0, /*tp_iternext*/
0, /*tp_methods*/
0, /*tp_members*/
0, /*tp_getset*/
0, /* see PyInit_xx */ /*tp_base*/
0, /*tp_dict*/
0, /*tp_descr_get*/
0, /*tp_descr_set*/
0, /*tp_dictoffset*/
0, /*tp_init*/
0, /*tp_alloc*/
0, /*tp_new*/
0, /*tp_free*/
0, /*tp_is_gc*/
};
/* ---------- */
static PyObject *
null_richcompare(PyObject *self, PyObject *other, int op)
{
Py_INCREF(Py_NotImplemented);
return Py_NotImplemented;
}
static PyTypeObject Null_Type = {
PyVarObject_HEAD_INIT(NULL, 0)
"pooptmodule.Null", /*tp_name*/
0, /*tp_basicsize*/
0, /*tp_itemsize*/
/* methods */
0, /*tp_dealloc*/
0, /*tp_print*/
0, /*tp_getattr*/
0, /*tp_setattr*/
0, /*tp_reserved*/
0, /*tp_repr*/
0, /*tp_as_number*/
0, /*tp_as_sequence*/
0, /*tp_as_mapping*/
0, /*tp_hash*/
0, /*tp_call*/
0, /*tp_str*/
0, /*tp_getattro*/
0, /*tp_setattro*/
0, /*tp_as_buffer*/
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
0, /*tp_doc*/
0, /*tp_traverse*/
0, /*tp_clear*/
null_richcompare, /*tp_richcompare*/
0, /*tp_weaklistoffset*/
0, /*tp_iter*/
0, /*tp_iternext*/
0, /*tp_methods*/
0, /*tp_members*/
0, /*tp_getset*/
0, /* see PyInit_xx */ /*tp_base*/
0, /*tp_dict*/
0, /*tp_descr_get*/
0, /*tp_descr_set*/
0, /*tp_dictoffset*/
0, /*tp_init*/
0, /*tp_alloc*/
0, /* see PyInit_xx */ /*tp_new*/
0, /*tp_free*/
0, /*tp_is_gc*/
};
/* List of functions defined in the module */
static PyMethodDef poopt_methods[] = {
{"cut_text", poopt_cut_text, METH_VARARGS,
poopt_cut_text_doc},
{NULL, NULL} /* sentinel */
};
PyDoc_STRVAR(module_doc,
"This is a template module just for instruction. And poopt.");
/* Initialization function for the module (*must* be called PyInit_xx) */
static struct PyModuleDef pooptmodule = {
PyModuleDef_HEAD_INIT,
"poopt",
module_doc,
-1,
poopt_methods,
NULL,
NULL,
NULL,
NULL
};
PyMODINIT_FUNC
PyInit_poopt(void)
{
PyObject *m = NULL;
/* Due to cross platform compiler issues the slots must be filled
* here. It's required for portability to Windows without requiring
* C++. */
Null_Type.tp_base = &PyBaseObject_Type;
Null_Type.tp_new = PyType_GenericNew;
Str_Type.tp_base = &PyUnicode_Type;
/* Finalize the type object including setting type of the new type
* object; doing it here is required for portability, too. */
/* if (PyType_Ready(&Xxo_Type) < 0) */
/* goto fail; */
/* Create the module and add the functions */
m = PyModule_Create(&pooptmodule);
if (m == NULL)
goto fail;
/* Add some symbolic constants to the module */
if (ErrorObject == NULL) {
ErrorObject = PyErr_NewException("poopt.error", NULL, NULL);
if (ErrorObject == NULL)
goto fail;
}
Py_INCREF(ErrorObject);
PyModule_AddObject(m, "error", ErrorObject);
/* Add Str */
if (PyType_Ready(&Str_Type) < 0)
goto fail;
PyModule_AddObject(m, "Str", (PyObject *)&Str_Type);
/* Add Null */
if (PyType_Ready(&Null_Type) < 0)
goto fail;
PyModule_AddObject(m, "Null", (PyObject *)&Null_Type);
return m;
fail:
Py_XDECREF(m);
return NULL;
}
/* /\* test function *\/ */
/* int main(void) */
/* { */
/* char coucou[] = "vive le foutre, le beurre et le caca boudin"; */
/* cut_text(coucou, 8); */
/* } */

View file

@ -120,7 +120,9 @@ class Room(TextBuffer):
nick_color = highlight nick_color = highlight
time = time or datetime.now() time = time or datetime.now()
message = Message(txt='%s\x19o'%(txt,), nick_color=nick_color, message = Message(txt='%s\x19o'%(txt,), nick_color=nick_color,
time=time, nickname=nickname, user=user) time=time, str_time=time.strftime("%Y-%m-%d %H:%M:%S")\
if history else time.strftime("%H:%M:%S"),\
nickname=nickname, user=user)
while len(self.messages) > self.messages_nb_limit: while len(self.messages) > self.messages_nb_limit:
self.messages.pop(0) self.messages.pop(0)
self.messages.append(message) self.messages.append(message)

View file

@ -14,7 +14,7 @@ Windows are displayed, resized, etc
""" """
MIN_WIDTH = 50 MIN_WIDTH = 50
MIN_HEIGHT = 16 MIN_HEIGHT = 22
import logging import logging
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -89,6 +89,10 @@ class Tab(object):
@staticmethod @staticmethod
def resize(scr): def resize(scr):
Tab.size = (Tab.height, Tab.width) = scr.getmaxyx() Tab.size = (Tab.height, Tab.width) = scr.getmaxyx()
if Tab.height < MIN_HEIGHT or Tab.width < MIN_WIDTH:
Tab.visible = False
else:
Tab.visible = True
def complete_commands(self, the_input): def complete_commands(self, the_input):
""" """
@ -671,6 +675,8 @@ class MucTab(ChatTab):
""" """
Resize the whole window. i.e. all its sub-windows Resize the whole window. i.e. all its sub-windows
""" """
if not self.visible:
return
text_width = (self.width//10)*9 text_width = (self.width//10)*9
self.topic_win.resize(1, self.width, 0, 0) self.topic_win.resize(1, self.width, 0, 0)
self.v_separator.resize(self.height-3, 1, 1, 9*(self.width//10)) self.v_separator.resize(self.height-3, 1, 1, 9*(self.width//10))
@ -1018,7 +1024,7 @@ class PrivateTab(ChatTab):
self.core.close_tab() self.core.close_tab()
def resize(self): def resize(self):
if self.core.information_win_size >= self.height-3: if self.core.information_win_size >= self.height-3 or not self.visible:
return return
self.text_win.resize(self.height-3-self.core.information_win_size, self.width, 0, 0) self.text_win.resize(self.height-3-self.core.information_win_size, self.width, 0, 0)
self.text_win.rebuild_everything(self._room) self.text_win.rebuild_everything(self._room)
@ -1165,6 +1171,8 @@ class RosterInfoTab(Tab):
self.resize() self.resize()
def resize(self): def resize(self):
if not self.visible:
return
roster_width = self.width//2 roster_width = self.width//2
info_width = self.width-roster_width-1 info_width = self.width-roster_width-1
self.v_separator.resize(self.height-2, 1, 0, roster_width) self.v_separator.resize(self.height-2, 1, 0, roster_width)
@ -1518,7 +1526,7 @@ class ConversationTab(ChatTab):
self.core.close_tab() self.core.close_tab()
def resize(self): def resize(self):
if self.core.information_win_size >= self.height-3: if self.core.information_win_size >= self.height-3 or not self.visible:
return return
self.text_win.resize(self.height-4-self.core.information_win_size, self.width, 1, 0) self.text_win.resize(self.height-4-self.core.information_win_size, self.width, 1, 0)
self.text_win.rebuild_everything(self._room) self.text_win.rebuild_everything(self._room)
@ -1635,6 +1643,8 @@ class MucListTab(Tab):
self.input.refresh() self.input.refresh()
def resize(self): def resize(self):
if not self.visible:
return
self.upper_message.resize(1, self.width, 0, 0) self.upper_message.resize(1, self.width, 0, 0)
column_size = {'node-part': (self.width-5)//4, column_size = {'node-part': (self.width-5)//4,
'name': (self.width-5)//4*3, 'name': (self.width-5)//4*3,
@ -1761,6 +1771,8 @@ class SimpleTextTab(Tab):
self.core.close_tab() self.core.close_tab()
def resize(self): def resize(self):
if not self.visible:
return
self.text_win.resize(self.height-2, self.width, 0, 0) self.text_win.resize(self.height-2, self.width, 0, 0)
self.input.resize(1, self.width, self.height-1, 0) self.input.resize(1, self.width, self.height-1, 0)

View file

@ -27,7 +27,7 @@ from datetime import datetime
import theme import theme
from config import config from config import config
Message = collections.namedtuple('Message', 'txt nick_color time nickname user') Message = collections.namedtuple('Message', 'txt nick_color time str_time nickname user')
class TextBuffer(object): class TextBuffer(object):
""" """
@ -45,8 +45,11 @@ class TextBuffer(object):
self.windows.append(win) self.windows.append(win)
def add_message(self, txt, time=None, nickname=None, nick_color=None, history=None): def add_message(self, txt, time=None, nickname=None, nick_color=None, history=None):
time = time or datetime.now()
msg = Message(txt='%s\x19o'%(txt,), nick_color=nick_color, msg = Message(txt='%s\x19o'%(txt,), nick_color=nick_color,
time=time or datetime.now(), nickname=nickname, user=None) time=time, str_time=time.strftime("%Y-%m-%d %H:%M:%S")\
if history else time.strftime("%H:%M:%S"),\
nickname=nickname, user=None)
self.messages.append(msg) self.messages.append(msg)
while len(self.messages) > self.messages_nb_limit: while len(self.messages) > self.messages_nb_limit:
self.messages.pop(0) self.messages.pop(0)

View file

@ -28,6 +28,7 @@ from threading import Lock
from contact import Contact, Resource from contact import Contact, Resource
from roster import RosterGroup, roster from roster import RosterGroup, roster
from poopt import cut_text
# from message import Line # from message import Line
from tabs import MIN_WIDTH, MIN_HEIGHT from tabs import MIN_WIDTH, MIN_HEIGHT
@ -41,7 +42,10 @@ import wcwidth
import singleton import singleton
import collections import collections
Line = collections.namedtuple('Line', 'text text_offset nickname_color time nickname') # 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.
Line = collections.namedtuple('Line', 'msg start_pos end_pos first')
g_lock = Lock() g_lock = Lock()
@ -89,6 +93,12 @@ class Win(object):
except: except:
pass pass
def move(self, y, x):
try:
self._win.move(y, x)
except:
self._win.move(0, 0)
def addstr_colored(self, text, y=None, x=None): def addstr_colored(self, text, y=None, x=None):
""" """
Write a string on the window, setting the Write a string on the window, setting the
@ -101,7 +111,7 @@ class Win(object):
one of 'u', 'b', 'c[0-9]' one of 'u', 'b', 'c[0-9]'
""" """
if y is not None and x is not None: if y is not None and x is not None:
self._win.move(y, x) self.move(y, x)
next_attr_char = text.find('\x19') next_attr_char = text.find('\x19')
while next_attr_char != -1: while next_attr_char != -1:
if next_attr_char + 1 < len(text): if next_attr_char + 1 < len(text):
@ -529,30 +539,14 @@ class TextWin(Win):
if None not in self.built_lines: if None not in self.built_lines:
self.built_lines.append(None) self.built_lines.append(None)
def build_new_message(self, message, history=None): def build_new_message(self, message, history=None, clean=True):
""" """
Take one message, build it and add it to the list Take one message, build it and add it to the list
Return the number of lines that are built for the given Return the number of lines that are built for the given
message. message.
""" """
def cut_text(text, width):
"""
returns the text that should be displayed on the line, and the rest
of the text, in a tuple
"""
cutted = wcwidth.widthcut(text, width) or text[:width]
limit = cutted.find('\n')
if limit >= 0:
return (text[limit+1:], text[:limit])
if not wcwidth.wcsislonger(text, width):
return ('', text)
limit = cutted.rfind(' ')
if limit <= 0:
return (text[len(cutted):], cutted)
else:
return (text[limit+1:], text[:limit])
if message is None: # line separator if message is None: # line separator
log.debug('je build NON, cool non ? +++++++++++++++++++++++++++')
self.built_lines.append(None) self.built_lines.append(None)
return 0 return 0
txt = message.txt txt = message.txt
@ -560,53 +554,32 @@ class TextWin(Win):
return 0 return 0
else: else:
txt = txt.replace('\t', ' ') txt = txt.replace('\t', ' ')
# length of the time nick = message.nickname
if history: if nick and len(nick) >= 25:
offset = 20 nick = nick[:25]+''
else: offset = 1 + len(message.str_time)
offset = 9
if theme.CHAR_TIME_RIGHT:
offset += 1
if theme.CHAR_TIME_RIGHT:
offset += 1
nickname = message.nickname
if nickname and len(nickname) >= 25:
nick = nickname[:25]+''
else:
nick = nickname
if nick: if nick:
offset += wcwidth.wcswidth(nick) + 2 # + nick + spaces length offset += wcwidth.wcswidth(nick) + 2 # + nick + spaces length
if nick:
offset += wcwidth.wcswidth(nick) + 2 # + nick + spaces length
if theme.CHAR_TIME_LEFT:
offset += 1
if theme.CHAR_TIME_RIGHT:
offset += 1
lines = cut_text(txt, self.width-offset-1)
first = True first = True
nb = 0 for line in lines:
while txt != '': self.built_lines.append(Line(msg=message,
(txt, cutted_txt) = cut_text(txt, self.width-offset-1) start_pos=line[0],
if first: end_pos=line[1],
if message.nick_color: first=first))
color = message.nick_color
elif message.user:
color = message.user.color
else:
color = None
else:
color = None
if first:
if history:
time = message.time.strftime("%Y-%m-%d %H:%M:%S")
else:
time = message.time.strftime("%H:%M:%S")
nickname = nick
else:
time = None
nickname = None
self.built_lines.append(Line(text=cutted_txt,
text_offset=offset,
nickname_color=color, time=time,
nickname=nickname))
nb += 1
first = False first = False
while len(self.built_lines) > self.lines_nb_limit: if clean:
self.built_lines.pop(0) while len(self.built_lines) > self.lines_nb_limit:
return nb self.built_lines.pop(0)
return len(lines)
def refresh(self, room): def refresh(self, room):
log.debug('Refresh: %s'%self.__class__.__name__) log.debug('Refresh: %s'%self.__class__.__name__)
@ -621,17 +594,26 @@ class TextWin(Win):
self._win.erase() self._win.erase()
for y, line in enumerate(lines): for y, line in enumerate(lines):
if line is None: if line is None:
log.debug('COUCOU JE SUIS NONE\n\n-----------------')
self.write_line_separator() self.write_line_separator()
else: else:
self.write_time(line.time) msg = line.msg
self.write_nickname(line.nickname, line.nickname_color) if line.first:
if msg.nick_color:
color = msg.nick_color
elif msg.user:
color = msg.user.color
else:
color = None
self.write_time(msg.str_time)
self.write_nickname(msg.nickname, color)
if y != self.height-1: if y != self.height-1:
self.addstr('\n') self.addstr('\n')
self._win.attrset(0) self._win.attrset(0)
for y, line in enumerate(lines): for y, line in enumerate(lines):
if not line: if not line:
continue continue
self.write_text(y, line.text_offset, line.text) self.write_text(y, (3 if line.msg.nickname else 1) + len(line.msg.str_time)+len(line.msg.nickname or ''), line.msg.txt[line.start_pos:line.end_pos])
if y != self.height-1: if y != self.height-1:
self.addstr('\n') self.addstr('\n')
self._win.attrset(0) self._win.attrset(0)
@ -676,7 +658,9 @@ class TextWin(Win):
def rebuild_everything(self, room): def rebuild_everything(self, room):
self.built_lines = [] self.built_lines = []
for message in room.messages: for message in room.messages:
self.build_new_message(message) self.build_new_message(message, clean=False)
while len(self.built_lines) > self.lines_nb_limit:
self.built_lines.pop(0)
def __del__(self): def __del__(self):
log.debug('** TextWin: deleting %s built lines' % (len(self.built_lines))) log.debug('** TextWin: deleting %s built lines' % (len(self.built_lines)))