summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFlorent Le Coz <louiz@louiz.org>2011-09-11 04:22:04 +0200
committerFlorent Le Coz <louiz@louiz.org>2011-09-11 04:22:04 +0200
commitb7b1faebdb52e8f041867f19f29a08f64cfaf220 (patch)
tree180c45399c1907c853bdf25585ec203c04fc8e9e
parent78c216cd61ab96f7476ce194323517b73b8ee04c (diff)
parentce1fb8af014e90cfc7378022c8cbdd134e45ed4a (diff)
downloadpoezio-b7b1faebdb52e8f041867f19f29a08f64cfaf220.tar.gz
poezio-b7b1faebdb52e8f041867f19f29a08f64cfaf220.tar.bz2
poezio-b7b1faebdb52e8f041867f19f29a08f64cfaf220.tar.xz
poezio-b7b1faebdb52e8f041867f19f29a08f64cfaf220.zip
merge
-rw-r--r--Makefile2
-rw-r--r--setup.py16
-rw-r--r--src/core.py11
-rw-r--r--src/pooptmodule.c279
-rw-r--r--src/room.py4
-rw-r--r--src/tabs.py18
-rw-r--r--src/text_buffer.py7
-rw-r--r--src/windows.py116
8 files changed, 379 insertions, 74 deletions
diff --git a/Makefile b/Makefile
index a327413a..8c0935f0 100644
--- a/Makefile
+++ b/Makefile
@@ -6,6 +6,8 @@ LOCALEDIR=$(DATADIR)/locale
MANDIR=$(DATADIR)/man
all: Makefile
+ python3 setup.py build
+ cp build/lib.linux-x86_64-3.2/poopt.cpython-32mu.so src/
clean:
find ./ -name \*.pyc -delete
diff --git a/setup.py b/setup.py
new file mode 100644
index 00000000..50d787da
--- /dev/null
+++ b/setup.py
@@ -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.
+ """)
diff --git a/src/core.py b/src/core.py
index a8fa65a5..8d1fde3a 100644
--- a/src/core.py
+++ b/src/core.py
@@ -355,7 +355,8 @@ class Core(object):
return
log.debug('on_got_offline: %s' % presence)
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
# precise resource.
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.edit_groups_of_contact(contact, [])
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')
if isinstance(self.current_tab(), tabs.RosterInfoTab):
self.refresh_window()
@@ -777,6 +778,12 @@ class Core(object):
return tab
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):
"""
returns the room that has this name
diff --git a/src/pooptmodule.c b/src/pooptmodule.c
new file mode 100644
index 00000000..289313f3
--- /dev/null
+++ b/src/pooptmodule.c
@@ -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); */
+/* } */
diff --git a/src/room.py b/src/room.py
index 7719dd67..1ca3f599 100644
--- a/src/room.py
+++ b/src/room.py
@@ -120,7 +120,9 @@ class Room(TextBuffer):
nick_color = highlight
time = time or datetime.now()
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:
self.messages.pop(0)
self.messages.append(message)
diff --git a/src/tabs.py b/src/tabs.py
index 9e55deff..c336adcd 100644
--- a/src/tabs.py
+++ b/src/tabs.py
@@ -14,7 +14,7 @@ Windows are displayed, resized, etc
"""
MIN_WIDTH = 50
-MIN_HEIGHT = 16
+MIN_HEIGHT = 22
import logging
log = logging.getLogger(__name__)
@@ -89,6 +89,10 @@ class Tab(object):
@staticmethod
def resize(scr):
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):
"""
@@ -671,6 +675,8 @@ class MucTab(ChatTab):
"""
Resize the whole window. i.e. all its sub-windows
"""
+ if not self.visible:
+ return
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))
@@ -1018,7 +1024,7 @@ class PrivateTab(ChatTab):
self.core.close_tab()
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
self.text_win.resize(self.height-3-self.core.information_win_size, self.width, 0, 0)
self.text_win.rebuild_everything(self._room)
@@ -1165,6 +1171,8 @@ class RosterInfoTab(Tab):
self.resize()
def resize(self):
+ if not self.visible:
+ return
roster_width = self.width//2
info_width = self.width-roster_width-1
self.v_separator.resize(self.height-2, 1, 0, roster_width)
@@ -1518,7 +1526,7 @@ class ConversationTab(ChatTab):
self.core.close_tab()
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
self.text_win.resize(self.height-4-self.core.information_win_size, self.width, 1, 0)
self.text_win.rebuild_everything(self._room)
@@ -1635,6 +1643,8 @@ class MucListTab(Tab):
self.input.refresh()
def resize(self):
+ if not self.visible:
+ return
self.upper_message.resize(1, self.width, 0, 0)
column_size = {'node-part': (self.width-5)//4,
'name': (self.width-5)//4*3,
@@ -1761,6 +1771,8 @@ class SimpleTextTab(Tab):
self.core.close_tab()
def resize(self):
+ if not self.visible:
+ return
self.text_win.resize(self.height-2, self.width, 0, 0)
self.input.resize(1, self.width, self.height-1, 0)
diff --git a/src/text_buffer.py b/src/text_buffer.py
index a3b5b1fb..a6465da3 100644
--- a/src/text_buffer.py
+++ b/src/text_buffer.py
@@ -27,7 +27,7 @@ from datetime import datetime
import theme
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):
"""
@@ -45,8 +45,11 @@ class TextBuffer(object):
self.windows.append(win)
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,
- 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)
while len(self.messages) > self.messages_nb_limit:
self.messages.pop(0)
diff --git a/src/windows.py b/src/windows.py
index 8e21d0f8..5b673c21 100644
--- a/src/windows.py
+++ b/src/windows.py
@@ -28,6 +28,7 @@ from threading import Lock
from contact import Contact, Resource
from roster import RosterGroup, roster
+from poopt import cut_text
# from message import Line
from tabs import MIN_WIDTH, MIN_HEIGHT
@@ -41,7 +42,10 @@ import wcwidth
import singleton
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()
@@ -89,6 +93,12 @@ class Win(object):
except:
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):
"""
Write a string on the window, setting the
@@ -101,7 +111,7 @@ class Win(object):
one of 'u', 'b', 'c[0-9]'
"""
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')
while next_attr_char != -1:
if next_attr_char + 1 < len(text):
@@ -529,30 +539,14 @@ class TextWin(Win):
if None not in self.built_lines:
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
Return the number of lines that are built for the given
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
+ log.debug('je build NON, cool non ? +++++++++++++++++++++++++++')
self.built_lines.append(None)
return 0
txt = message.txt
@@ -560,53 +554,32 @@ class TextWin(Win):
return 0
else:
txt = txt.replace('\t', ' ')
- # length of the time
- if history:
- offset = 20
- else:
- offset = 9
- if theme.CHAR_TIME_RIGHT:
+ nick = message.nickname
+ if nick and len(nick) >= 25:
+ nick = nick[:25]+'…'
+ offset = 1 + len(message.str_time)
+ if nick:
+ 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
- nickname = message.nickname
- if nickname and len(nickname) >= 25:
- nick = nickname[:25]+'…'
- else:
- nick = nickname
- if nick:
- offset += wcwidth.wcswidth(nick) + 2 # + nick + spaces length
+
+ lines = cut_text(txt, self.width-offset-1)
+
first = True
- nb = 0
- while txt != '':
- (txt, cutted_txt) = cut_text(txt, self.width-offset-1)
- if first:
- if message.nick_color:
- 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
+ for line in lines:
+ self.built_lines.append(Line(msg=message,
+ start_pos=line[0],
+ end_pos=line[1],
+ first=first))
first = False
- while len(self.built_lines) > self.lines_nb_limit:
- self.built_lines.pop(0)
- return nb
+ if clean:
+ while len(self.built_lines) > self.lines_nb_limit:
+ self.built_lines.pop(0)
+ return len(lines)
def refresh(self, room):
log.debug('Refresh: %s'%self.__class__.__name__)
@@ -621,17 +594,26 @@ class TextWin(Win):
self._win.erase()
for y, line in enumerate(lines):
if line is None:
+ log.debug('COUCOU JE SUIS NONE\n\n-----------------')
self.write_line_separator()
else:
- self.write_time(line.time)
- self.write_nickname(line.nickname, line.nickname_color)
+ msg = line.msg
+ 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:
self.addstr('\n')
self._win.attrset(0)
for y, line in enumerate(lines):
if not line:
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:
self.addstr('\n')
self._win.attrset(0)
@@ -676,7 +658,9 @@ class TextWin(Win):
def rebuild_everything(self, room):
self.built_lines = []
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):
log.debug('** TextWin: deleting %s built lines' % (len(self.built_lines)))