summaryrefslogtreecommitdiff
path: root/src/windows
diff options
context:
space:
mode:
Diffstat (limited to 'src/windows')
-rw-r--r--src/windows/__init__.py5
-rw-r--r--src/windows/bookmark_forms.py278
-rw-r--r--src/windows/data_forms.py1
-rw-r--r--src/windows/funcs.py4
-rw-r--r--src/windows/info_bar.py7
-rw-r--r--src/windows/info_wins.py14
-rw-r--r--src/windows/input_placeholders.py16
-rw-r--r--src/windows/inputs.py7
-rw-r--r--src/windows/muc.py9
-rw-r--r--src/windows/roster_win.py19
-rw-r--r--src/windows/text_win.py309
11 files changed, 552 insertions, 117 deletions
diff --git a/src/windows/__init__.py b/src/windows/__init__.py
index 9e165201..5ec73961 100644
--- a/src/windows/__init__.py
+++ b/src/windows/__init__.py
@@ -5,15 +5,16 @@ used to display information on the screen
from . base_wins import Win
from . data_forms import FormWin
+from . bookmark_forms import BookmarksWin
from . info_bar import GlobalInfoBar, VerticalGlobalInfoBar
from . info_wins import InfoWin, XMLInfoWin, PrivateInfoWin, MucListInfoWin, \
ConversationInfoWin, DynamicConversationInfoWin, MucInfoWin, \
- ConversationStatusMessageWin
+ ConversationStatusMessageWin, BookmarksInfoWin
from . input_placeholders import HelpText, YesNoInput
from . inputs import Input, HistoryInput, MessageInput, CommandInput
from . list import ListWin, ColumnHeaderWin
from . misc import VerticalSeparator
from . muc import UserList, Topic
from . roster_win import RosterWin, ContactInfoWin
-from . text_win import TextWin
+from . text_win import TextWin, XMLTextWin
diff --git a/src/windows/bookmark_forms.py b/src/windows/bookmark_forms.py
new file mode 100644
index 00000000..7cbd30cc
--- /dev/null
+++ b/src/windows/bookmark_forms.py
@@ -0,0 +1,278 @@
+"""
+Windows used inthe bookmarkstab
+"""
+import curses
+
+from . import Win
+from . inputs import Input
+from . data_forms import FieldInput
+from theming import to_curses_attr, get_theme
+from common import safeJID
+
+class BookmarkJIDInput(FieldInput, Input):
+ def __init__(self, field):
+ FieldInput.__init__(self, field)
+ Input.__init__(self)
+ jid = safeJID(field.jid)
+ jid.resource = field.nick
+ self.text = jid.full
+ self.pos = len(self.text)
+ self.color = get_theme().COLOR_NORMAL_TEXT
+
+ def save(self):
+ jid = safeJID(self.get_text())
+ self._field.jid = jid.bare
+ self._field.name = jid.bare
+ self._field.nick = jid.resource
+
+ def get_help_message(self):
+ return 'Edit the text'
+
+class BookmarkMethodInput(FieldInput, Win):
+ def __init__(self, field):
+ FieldInput.__init__(self, field)
+ Win.__init__(self)
+ self.options = ('local', 'remote')
+ # val_pos is the position of the currently selected option
+ self.val_pos = self.options.index(field.method)
+
+ def do_command(self, key):
+ if key == 'KEY_LEFT':
+ if self.val_pos > 0:
+ self.val_pos -= 1
+ elif key == 'KEY_RIGHT':
+ if self.val_pos < len(self.options)-1:
+ self.val_pos += 1
+ else:
+ return
+ self.refresh()
+
+ def refresh(self):
+ self._win.erase()
+ self._win.attron(to_curses_attr(self.color))
+ self.addnstr(0, 0, ' '*self.width, self.width)
+ if self.val_pos > 0:
+ self.addstr(0, 0, '←')
+ if self.val_pos < len(self.options)-1:
+ self.addstr(0, self.width-1, '→')
+ if self.options:
+ option = self.options[self.val_pos]
+ self.addstr(0, self.width//2-len(option)//2, option)
+ self._win.attroff(to_curses_attr(self.color))
+ self._refresh()
+
+ def save(self):
+ self._field.method = self.options[self.val_pos]
+
+ def get_help_message(self):
+ return '←, →: Select a value amongst the others'
+
+class BookmarkPasswordInput(FieldInput, Input):
+ def __init__(self, field):
+ FieldInput.__init__(self, field)
+ Input.__init__(self)
+ self.text = field.password or ''
+ self.pos = len(self.text)
+ self.color = get_theme().COLOR_NORMAL_TEXT
+
+ def rewrite_text(self):
+ self._win.erase()
+ if self.color:
+ self._win.attron(to_curses_attr(self.color))
+ self.addstr('*'*len(self.text[self.view_pos:self.view_pos+self.width-1]))
+ if self.color:
+ (y, x) = self._win.getyx()
+ size = self.width-x
+ self.addnstr(' '*size, size, to_curses_attr(self.color))
+ self.addstr(0, self.pos, '')
+ if self.color:
+ self._win.attroff(to_curses_attr(self.color))
+ self._refresh()
+
+ def save(self):
+ self._field.password = self.get_text() or None
+
+ def get_help_message(self):
+ return 'Edit the secret text'
+
+class BookmarkAutojoinWin(FieldInput, Win):
+ def __init__(self, field):
+ FieldInput.__init__(self, field)
+ Win.__init__(self)
+ self.last_key = 'KEY_RIGHT'
+ self.value = field.autojoin
+
+ def do_command(self, key):
+ if key == 'KEY_LEFT' or key == 'KEY_RIGHT':
+ self.value = not self.value
+ self.last_key = key
+ self.refresh()
+
+ def refresh(self):
+ self._win.erase()
+ self._win.attron(to_curses_attr(self.color))
+ format_string = '←{:^%s}→' % 7
+ inp = format_string.format(repr(self.value))
+ self.addstr(0, 0, inp)
+ if self.last_key == 'KEY_RIGHT':
+ self.move(0, 8)
+ else:
+ self.move(0, 0)
+ self._win.attroff(to_curses_attr(self.color))
+ self._refresh()
+
+ def save(self):
+ self._field.autojoin = self.value
+
+ def get_help_message(self):
+ return '← and →: change the value between True and False'
+
+
+class BookmarksWin(Win):
+ def __init__(self, bookmarks, height, width, y, x):
+ self._win = Win._tab_win.derwin(height, width, y, x)
+ self.scroll_pos = 0
+ self._current_input = 0
+ self.current_horizontal_input = 0
+ self._bookmarks = list(bookmarks)
+ self.lines = []
+ for bookmark in sorted(self._bookmarks, key=lambda x: x.jid):
+ self.lines.append((BookmarkJIDInput(bookmark),
+ BookmarkPasswordInput(bookmark),
+ BookmarkAutojoinWin(bookmark),
+ BookmarkMethodInput(bookmark)))
+
+ @property
+ def current_input(self):
+ return self._current_input
+
+ @current_input.setter
+ def current_input(self, value):
+ if 0 <= self._current_input < len(self.lines):
+ if 0 <= value < len(self.lines):
+ self.lines[self._current_input][self.current_horizontal_input].set_color(get_theme().COLOR_NORMAL_TEXT)
+ self._current_input = value
+ else:
+ self._current_input = 0
+
+ def add_bookmark(self, bookmark):
+ self.lines.append((BookmarkJIDInput(bookmark),
+ BookmarkPasswordInput(bookmark),
+ BookmarkAutojoinWin(bookmark),
+ BookmarkMethodInput(bookmark)))
+ self.lines[self.current_input][self.current_horizontal_input].set_color(get_theme().COLOR_NORMAL_TEXT)
+ self.current_horizontal_input = 0
+ self.current_input = len(self.lines) - 1
+ if self.current_input - self.scroll_pos > self.height-1:
+ self.scroll_pos = self.current_input - self.height + 1
+ self.refresh()
+
+ def del_current_bookmark(self):
+ if self.lines:
+ bm = self.lines[self.current_input][0]._field
+ to_delete = self.current_input
+ self.current_input -= 1
+ del self.lines[to_delete]
+ if self.scroll_pos:
+ self.scroll_pos -= 1
+ self.refresh()
+ return bm
+
+ def resize(self, height, width, y, x):
+ self.height = height
+ self.width = width
+ self._win = Win._tab_win.derwin(height, width, y, x)
+ # Adjust the scroll position, if resizing made the window too small
+ # for the cursor to be visible
+ while self.current_input - self.scroll_pos > self.height-1:
+ self.scroll_pos += 1
+
+ def go_to_next_line_input(self):
+ if not self.lines:
+ return
+ if self.current_input == len(self.lines) - 1:
+ return
+ self.lines[self.current_input][self.current_horizontal_input].set_color(get_theme().COLOR_NORMAL_TEXT)
+ # Adjust the scroll position if the current_input would be outside
+ # of the visible area
+ if self.current_input + 1 - self.scroll_pos > self.height-1:
+ self.current_input += 1
+ self.scroll_pos += 1
+ self.refresh()
+ else:
+ self.current_input += 1
+ self.lines[self.current_input][self.current_horizontal_input].set_color(get_theme().COLOR_SELECTED_ROW)
+
+ def go_to_previous_line_input(self):
+ if not self.lines:
+ return
+ if self.current_input == 0:
+ return
+ self.lines[self.current_input][self.current_horizontal_input].set_color(get_theme().COLOR_NORMAL_TEXT)
+ self.current_input -= 1
+ # Adjust the scroll position if the current_input would be outside
+ # of the visible area
+ if self.current_input < self.scroll_pos:
+ self.scroll_pos = self.current_input
+ self.refresh()
+ self.lines[self.current_input][self.current_horizontal_input].set_color(get_theme().COLOR_SELECTED_ROW)
+
+ def go_to_next_horizontal_input(self):
+ if not self.lines:
+ return
+ self.lines[self.current_input][self.current_horizontal_input].set_color(get_theme().COLOR_NORMAL_TEXT)
+ self.current_horizontal_input += 1
+ if self.current_horizontal_input > 3:
+ self.current_horizontal_input = 0
+ self.lines[self.current_input][self.current_horizontal_input].set_color(get_theme().COLOR_SELECTED_ROW)
+
+ def go_to_previous_horizontal_input(self):
+ if not self.lines:
+ return
+ if self.current_horizontal_input == 0:
+ return
+ self.lines[self.current_input][self.current_horizontal_input].set_color(get_theme().COLOR_NORMAL_TEXT)
+ self.current_horizontal_input -= 1
+ self.lines[self.current_input][self.current_horizontal_input].set_color(get_theme().COLOR_SELECTED_ROW)
+
+ def on_input(self, key):
+ if not self.lines:
+ return
+ self.lines[self.current_input][self.current_horizontal_input].do_command(key)
+
+ def refresh(self):
+ # store the cursor status
+ self._win.erase()
+ y = - self.scroll_pos
+ for i in range(len(self.lines)):
+ self.lines[i][0].resize(1, self.width//3, y + 1, 0)
+ self.lines[i][1].resize(1, self.width//3, y + 1, self.width//3)
+ self.lines[i][2].resize(1, self.width//6, y + 1, 2*self.width//3)
+ self.lines[i][3].resize(1, self.width//6, y + 1, 5*self.width//6)
+ y += 1
+ self._refresh()
+ for i, inp in enumerate(self.lines):
+ if i < self.scroll_pos:
+ continue
+ if i >= self.height + self.scroll_pos:
+ break
+ for j in range(4):
+ inp[j].refresh()
+
+ if self.lines and self.current_input < self.height-1:
+ self.lines[self.current_input][self.current_horizontal_input].set_color(get_theme().COLOR_SELECTED_ROW)
+ self.lines[self.current_input][self.current_horizontal_input].refresh()
+ if not self.lines:
+ curses.curs_set(0)
+ else:
+ curses.curs_set(1)
+
+ def refresh_current_input(self):
+ if self.lines:
+ self.lines[self.current_input][self.current_horizontal_input].refresh()
+
+ def save(self):
+ for line in self.lines:
+ for item in line:
+ item.save()
+
diff --git a/src/windows/data_forms.py b/src/windows/data_forms.py
index d6e2cc66..86f33350 100644
--- a/src/windows/data_forms.py
+++ b/src/windows/data_forms.py
@@ -469,4 +469,3 @@ class FormWin(object):
return self.inputs[self.current_input]['input'].get_help_message()
return ''
-
diff --git a/src/windows/funcs.py b/src/windows/funcs.py
index d58d4683..f1401628 100644
--- a/src/windows/funcs.py
+++ b/src/windows/funcs.py
@@ -4,7 +4,6 @@ Standalone functions used by the modules
import string
-from config import config
from . base_wins import FORMAT_CHAR, format_chars
def find_first_format_char(text, chars=None):
@@ -19,8 +18,7 @@ def find_first_format_char(text, chars=None):
pos = p
return pos
-def truncate_nick(nick, size=None):
- size = size or config.get('max_nick_length')
+def truncate_nick(nick, size=10):
if size < 1:
size = 1
if nick and len(nick) > size:
diff --git a/src/windows/info_bar.py b/src/windows/info_bar.py
index e66343c5..abd956cd 100644
--- a/src/windows/info_bar.py
+++ b/src/windows/info_bar.py
@@ -28,6 +28,7 @@ class GlobalInfoBar(Win):
show_names = config.get('show_tab_names')
show_nums = config.get('show_tab_numbers')
use_nicks = config.get('use_tab_nicks')
+ show_inactive = config.get('show_inactive_tabs')
# ignore any remaining gap tabs if the feature is not enabled
if create_gaps:
sorted_tabs = self.core.tabs[:]
@@ -37,8 +38,7 @@ class GlobalInfoBar(Win):
for nb, tab in enumerate(sorted_tabs):
if not tab: continue
color = tab.color
- if not config.get('show_inactive_tabs') and\
- color is get_theme().COLOR_TAB_NORMAL:
+ if not show_inactive and color is get_theme().COLOR_TAB_NORMAL:
continue
try:
if show_nums or not show_names:
@@ -87,9 +87,10 @@ class VerticalGlobalInfoBar(Win):
sorted_tabs = sorted_tabs[-height:]
else:
sorted_tabs = sorted_tabs[pos-height//2 : pos+height//2]
+ asc_sort = (config.get('vertical_tab_list_sort') == 'asc')
for y, tab in enumerate(sorted_tabs):
color = tab.vertical_color
- if not config.get('vertical_tab_list_sort') != 'asc':
+ if asc_sort:
y = height - y - 1
self.addstr(y, 0, "%2d" % tab.nb,
to_curses_attr(get_theme().COLOR_VERTICAL_TAB_NUMBER))
diff --git a/src/windows/info_wins.py b/src/windows/info_wins.py
index 766afb75..80af4602 100644
--- a/src/windows/info_wins.py
+++ b/src/windows/info_wins.py
@@ -293,3 +293,17 @@ class ConversationStatusMessageWin(InfoWin):
def write_status_message(self, resource):
self.addstr(resource.status, to_curses_attr(get_theme().COLOR_INFORMATION_BAR))
+class BookmarksInfoWin(InfoWin):
+ def __init__(self):
+ InfoWin.__init__(self)
+
+ def refresh(self, preferred):
+ log.debug('Refresh: %s', self.__class__.__name__)
+ self._win.erase()
+ self.write_remote_status(preferred)
+ self.finish_line(get_theme().COLOR_INFORMATION_BAR)
+ self._refresh()
+
+ def write_remote_status(self, preferred):
+ self.addstr('Remote storage: %s' % preferred, to_curses_attr(get_theme().COLOR_INFORMATION_BAR))
+
diff --git a/src/windows/input_placeholders.py b/src/windows/input_placeholders.py
index 8bcf1524..496417d1 100644
--- a/src/windows/input_placeholders.py
+++ b/src/windows/input_placeholders.py
@@ -41,7 +41,7 @@ class YesNoInput(Win):
A Window just displaying a Yes/No input
Used to ask a confirmation
"""
- def __init__(self, text=''):
+ def __init__(self, text='', callback=None):
Win.__init__(self)
self.key_func = {
'y' : self.on_yes,
@@ -49,6 +49,7 @@ class YesNoInput(Win):
}
self.txt = text
self.value = None
+ self.callback = callback
def on_yes(self):
self.value = True
@@ -68,17 +69,8 @@ class YesNoInput(Win):
def do_command(self, key, raw=False):
if key.lower() in self.key_func:
self.key_func[key]()
-
- def prompt(self):
- """Monopolizes the input while waiting for a recognized keypress"""
- def cb(key):
- if key in self.key_func:
- self.key_func[key]()
- if self.value is None:
- # We didn’t finish with this prompt, continue monopolizing
- # it again until value is set
- keyboard.continuation_keys_callback = cb
- keyboard.continuation_keys_callback = cb
+ if self.value is not None and self.callback is not None:
+ return self.callback()
def on_delete(self):
return
diff --git a/src/windows/inputs.py b/src/windows/inputs.py
index d345443b..12d3a9a2 100644
--- a/src/windows/inputs.py
+++ b/src/windows/inputs.py
@@ -43,6 +43,8 @@ class Input(Win):
'^D': self.key_dc,
'M-b': self.jump_word_left,
"M-[1;5D": self.jump_word_left,
+ "kRIT5": self.jump_word_right,
+ "kLFT5": self.jump_word_left,
'^W': self.delete_word,
'M-d': self.delete_next_word,
'^K': self.delete_end_of_line,
@@ -534,6 +536,11 @@ class Input(Win):
if self.view_pos < 0:
self.view_pos = 0
+ # text small enough to fit inside the window entirely:
+ # remove scrolling if present
+ if poopt.wcswidth(self.text) < self.width:
+ self.view_pos = 0
+
assert(self.pos >= self.view_pos and
self.pos <= self.view_pos + max(self.width, 3))
diff --git a/src/windows/muc.py b/src/windows/muc.py
index 7e3541ba..c4e8df6e 100644
--- a/src/windows/muc.py
+++ b/src/windows/muc.py
@@ -37,7 +37,8 @@ class UserList(Win):
if config.get('hide_user_list'):
return # do not refresh if this win is hidden.
self._win.erase()
- if config.get('user_list_sort').lower() == 'asc':
+ asc_sort = (config.get('user_list_sort').lower() == 'asc')
+ if asc_sort:
y, x = self._win.getmaxyx()
y -= 1
users = sorted(users)
@@ -55,7 +56,7 @@ class UserList(Win):
self.addstr(y, 2,
poopt.cut_by_columns(user.nick, self.width - 2),
to_curses_attr(user.color))
- if config.get('user_list_sort').lower() == 'asc':
+ if asc_sort:
y -= 1
else:
y += 1
@@ -63,12 +64,12 @@ class UserList(Win):
break
# draw indicators of position in the list
if self.pos > 0:
- if config.get('user_list_sort').lower() == 'asc':
+ if asc_sort:
self.draw_plus(self.height-1)
else:
self.draw_plus(0)
if self.pos + self.height < len(users):
- if config.get('user_list_sort').lower() == 'asc':
+ if asc_sort:
self.draw_plus(0)
else:
self.draw_plus(self.height-1)
diff --git a/src/windows/roster_win.py b/src/windows/roster_win.py
index 6ecb6128..a2e2badd 100644
--- a/src/windows/roster_win.py
+++ b/src/windows/roster_win.py
@@ -145,6 +145,12 @@ class RosterWin(Win):
# draw the roster from the cache
roster_view = self.roster_cache[self.start_pos-1:self.start_pos+self.height]
+ options = {
+ 'show_roster_sub': config.get('show_roster_subscriptions'),
+ 'show_s2s_errors': config.get('show_s2s_errors'),
+ 'show_roster_jids': config.get('show_roster_jids')
+ }
+
for item in roster_view:
draw_selected = False
if y -2 + self.start_pos == self.pos:
@@ -155,7 +161,7 @@ class RosterWin(Win):
self.draw_group(y, item, draw_selected)
group = item.name
elif isinstance(item, Contact):
- self.draw_contact_line(y, item, draw_selected, group)
+ self.draw_contact_line(y, item, draw_selected, group, **options)
elif isinstance(item, Resource):
self.draw_resource_line(y, item, draw_selected)
@@ -206,7 +212,8 @@ class RosterWin(Win):
return name
return name[:self.width - added - 1] + '…'
- def draw_contact_line(self, y, contact, colored, group):
+ def draw_contact_line(self, y, contact, colored, group, show_roster_sub=False,
+ show_s2s_errors=True, show_roster_jids=False):
"""
Draw on a line all informations about one contact.
This is basically the highest priority resource's informations
@@ -229,15 +236,13 @@ class RosterWin(Win):
self.addstr(y, 0, ' ')
self.addstr(theme.CHAR_STATUS, to_curses_attr(color))
- show_roster_sub = config.get('show_roster_subscriptions')
-
self.addstr(' ')
if resource:
self.addstr('[+] ' if contact.folded(group) else '[-] ')
added += 4
if contact.ask:
added += len(get_theme().CHAR_ROSTER_ASKED)
- if config.get('show_s2s_errors') and contact.error:
+ if show_s2s_errors and contact.error:
added += len(get_theme().CHAR_ROSTER_ERROR)
if contact.tune:
added += len(get_theme().CHAR_ROSTER_TUNE)
@@ -250,7 +255,7 @@ class RosterWin(Win):
if show_roster_sub in ('all', 'incomplete', 'to', 'from', 'both', 'none'):
added += len(theme.char_subscription(contact.subscription, keep=show_roster_sub))
- if not config.get('show_roster_jids') and contact.name:
+ if not show_roster_jids and contact.name:
display_name = '%s' % contact.name
elif contact.name and contact.name != contact.bare_jid:
display_name = '%s (%s)' % (contact.name, contact.bare_jid)
@@ -268,7 +273,7 @@ class RosterWin(Win):
self.addstr(theme.char_subscription(contact.subscription, keep=show_roster_sub), to_curses_attr(theme.COLOR_ROSTER_SUBSCRIPTION))
if contact.ask:
self.addstr(get_theme().CHAR_ROSTER_ASKED, to_curses_attr(get_theme().COLOR_IMPORTANT_TEXT))
- if config.get('show_s2s_errors') and contact.error:
+ if show_s2s_errors and contact.error:
self.addstr(get_theme().CHAR_ROSTER_ERROR, to_curses_attr(get_theme().COLOR_ROSTER_ERROR))
if contact.tune:
self.addstr(get_theme().CHAR_ROSTER_TUNE, to_curses_attr(get_theme().COLOR_ROSTER_TUNE))
diff --git a/src/windows/text_win.py b/src/windows/text_win.py
index 6fe74f41..59c5230b 100644
--- a/src/windows/text_win.py
+++ b/src/windows/text_win.py
@@ -18,7 +18,7 @@ from config import config
from theming import to_curses_attr, get_theme, dump_tuple
-class TextWin(Win):
+class BaseTextWin(Win):
def __init__(self, lines_nb_limit=None):
if lines_nb_limit is None:
lines_nb_limit = config.get('max_lines_in_memory')
@@ -30,19 +30,6 @@ class TextWin(Win):
self.lock = False
self.lock_buffer = []
-
- # the Lines of the highlights in that buffer
- self.highlights = []
- # the current HL position in that list NaN means that we’re not on
- # an hl. -1 is a valid position (it's before the first hl of the
- # list. i.e the separator, in the case where there’s no hl before
- # it.)
- self.hl_pos = float('nan')
-
- # Keep track of the number of hl after the separator.
- # This is useful to make “go to next highlight“ work after a “move to separator”.
- self.nb_of_highlights_after_separator = 0
-
self.separator_after = None
def toggle_lock(self):
@@ -60,6 +47,114 @@ class TextWin(Win):
self.built_lines.append(line)
self.lock = False
+ def scroll_up(self, dist=14):
+ pos = self.pos
+ self.pos += dist
+ if self.pos + self.height > len(self.built_lines):
+ self.pos = len(self.built_lines) - self.height
+ if self.pos < 0:
+ self.pos = 0
+ return self.pos != pos
+
+ def scroll_down(self, dist=14):
+ pos = self.pos
+ self.pos -= dist
+ if self.pos <= 0:
+ self.pos = 0
+ return self.pos != pos
+
+ def build_new_message(self, message, history=None, clean=True, highlight=False, timestamp=False, nick_size=10):
+ """
+ Take one message, build it and add it to the list
+ Return the number of lines that are built for the given
+ message.
+ """
+ lines = self.build_message(message, timestamp=timestamp, nick_size=nick_size)
+ if self.lock:
+ self.lock_buffer.extend(lines)
+ else:
+ self.built_lines.extend(lines)
+ if not lines or not lines[0]:
+ return 0
+ if clean:
+ while len(self.built_lines) > self.lines_nb_limit:
+ self.built_lines.pop(0)
+ return len(lines)
+
+ def build_message(self, message, timestamp=False, nick_size=10):
+ """
+ Build a list of lines from a message, without adding it
+ to a list
+ """
+ pass
+
+ def refresh(self):
+ pass
+
+ def write_text(self, y, x, txt):
+ """
+ write the text of a line.
+ """
+ self.addstr_colored(txt, y, x)
+
+ def write_time(self, time):
+ """
+ Write the date on the yth line of the window
+ """
+ if time:
+ self.addstr(time)
+ self.addstr(' ')
+
+ def resize(self, height, width, y, x, room=None):
+ if hasattr(self, 'width'):
+ old_width = self.width
+ else:
+ old_width = None
+ self._resize(height, width, y, x)
+ if room and self.width != old_width:
+ self.rebuild_everything(room)
+
+ # reposition the scrolling after resize
+ # (see #2450)
+ buf_size = len(self.built_lines)
+ if buf_size - self.pos < self.height:
+ self.pos = buf_size - self.height
+ if self.pos < 0:
+ self.pos = 0
+
+ def rebuild_everything(self, room):
+ self.built_lines = []
+ with_timestamps = config.get('show_timestamps')
+ nick_size = config.get('max_nick_length')
+ for message in room.messages:
+ self.build_new_message(message, clean=False, timestamp=with_timestamps, nick_size=nick_size)
+ if self.separator_after is message:
+ self.build_new_message(None)
+ 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)))
+ del self.built_lines
+
+class TextWin(BaseTextWin):
+ def __init__(self, lines_nb_limit=None):
+ BaseTextWin.__init__(self, lines_nb_limit)
+
+ # the Lines of the highlights in that buffer
+ self.highlights = []
+ # the current HL position in that list NaN means that we’re not on
+ # an hl. -1 is a valid position (it's before the first hl of the
+ # list. i.e the separator, in the case where there’s no hl before
+ # it.)
+ self.hl_pos = float('nan')
+
+ # Keep track of the number of hl after the separator.
+ # This is useful to make “go to next highlight“ work after a “move to separator”.
+ self.nb_of_highlights_after_separator = 0
+
+ self.separator_after = None
+
def next_highlight(self):
"""
Go to the next highlight in the buffer.
@@ -130,22 +225,6 @@ class TextWin(Win):
if self.pos < 0 or self.pos >= len(self.built_lines):
self.pos = 0
- def scroll_up(self, dist=14):
- pos = self.pos
- self.pos += dist
- if self.pos + self.height > len(self.built_lines):
- self.pos = len(self.built_lines) - self.height
- if self.pos < 0:
- self.pos = 0
- return self.pos != pos
-
- def scroll_down(self, dist=14):
- pos = self.pos
- self.pos -= dist
- if self.pos <= 0:
- self.pos = 0
- return self.pos != pos
-
def scroll_to_separator(self):
"""
Scroll until separator is centered. If no separator is
@@ -187,13 +266,13 @@ class TextWin(Win):
if room and room.messages:
self.separator_after = room.messages[-1]
- def build_new_message(self, message, history=None, clean=True, highlight=False, timestamp=False):
+ def build_new_message(self, message, history=None, clean=True, highlight=False, timestamp=False, nick_size=10):
"""
Take one message, build it and add it to the list
Return the number of lines that are built for the given
message.
"""
- lines = self.build_message(message, timestamp=timestamp)
+ lines = self.build_message(message, timestamp=timestamp, nick_size=nick_size)
if self.lock:
self.lock_buffer.extend(lines)
else:
@@ -210,7 +289,7 @@ class TextWin(Win):
self.built_lines.pop(0)
return len(lines)
- def build_message(self, message, timestamp=False):
+ def build_message(self, message, timestamp=False, nick_size=10):
"""
Build a list of lines from a message, without adding it
to a list
@@ -226,10 +305,13 @@ class TextWin(Win):
else:
default_color = None
ret = []
- nick = truncate_nick(message.nickname)
+ nick = truncate_nick(message.nickname, nick_size)
offset = 0
if message.ack:
- offset += poopt.wcswidth(get_theme().CHAR_ACK_RECEIVED) + 1
+ if message.ack > 0:
+ offset += poopt.wcswidth(get_theme().CHAR_ACK_RECEIVED) + 1
+ else:
+ offset += poopt.wcswidth(get_theme().CHAR_NACK) + 1
if nick:
offset += poopt.wcswidth(nick) + 2 # + nick + '> ' length
if message.revisions > 0:
@@ -268,12 +350,14 @@ class TextWin(Win):
else:
lines = self.built_lines[-self.height-self.pos:-self.pos]
with_timestamps = config.get("show_timestamps")
+ nick_size = config.get("max_nick_length")
self._win.move(0, 0)
self._win.erase()
for y, line in enumerate(lines):
if line:
msg = line.msg
if line.start_pos == 0:
+ nick = truncate_nick(msg.nickname, nick_size)
if msg.nick_color:
color = msg.nick_color
elif msg.user:
@@ -283,18 +367,21 @@ class TextWin(Win):
if with_timestamps:
self.write_time(msg.str_time)
if msg.ack:
- self.write_ack()
+ if msg.ack > 0:
+ self.write_ack()
+ else:
+ self.write_nack()
if msg.me:
self._win.attron(to_curses_attr(get_theme().COLOR_ME_MESSAGE))
self.addstr('* ')
- self.write_nickname(msg.nickname, color, msg.highlight)
+ self.write_nickname(nick, color, msg.highlight)
if msg.revisions:
self._win.attron(to_curses_attr(get_theme().COLOR_REVISIONS_MESSAGE))
self.addstr('%d' % msg.revisions)
self._win.attrset(0)
self.addstr(' ')
else:
- self.write_nickname(msg.nickname, color, msg.highlight)
+ self.write_nickname(nick, color, msg.highlight)
if msg.revisions:
self._win.attron(to_curses_attr(get_theme().COLOR_REVISIONS_MESSAGE))
self.addstr('%d' % msg.revisions)
@@ -317,8 +404,7 @@ class TextWin(Win):
# Offset for the nickname (if any)
# plus a space and a > after it
if line.msg.nickname:
- offset += poopt.wcswidth(
- truncate_nick(line.msg.nickname))
+ offset += poopt.wcswidth(truncate_nick(line.msg.nickname, nick_size))
if line.msg.me:
offset += 3
else:
@@ -326,8 +412,11 @@ class TextWin(Win):
offset += ceil(log10(line.msg.revisions + 1))
if line.msg.ack:
- offset += 1 + poopt.wcswidth(
- get_theme().CHAR_ACK_RECEIVED)
+ if msg.ack > 0:
+ offset += 1 + poopt.wcswidth(
+ get_theme().CHAR_ACK_RECEIVED)
+ else:
+ offset += 1 + poopt.wcswidth(get_theme().CHAR_NACK)
self.write_text(y, offset,
line.prepend+line.msg.txt[line.start_pos:line.end_pos])
@@ -343,12 +432,6 @@ class TextWin(Win):
self.width,
to_curses_attr(get_theme().COLOR_NEW_TEXT_SEPARATOR))
- def write_text(self, y, x, txt):
- """
- write the text of a line.
- """
- self.addstr_colored(txt, y, x)
-
def write_ack(self):
color = get_theme().COLOR_CHAR_ACK
self._win.attron(to_curses_attr(color))
@@ -356,6 +439,13 @@ class TextWin(Win):
self._win.attroff(to_curses_attr(color))
self.addstr(' ')
+ def write_nack(self):
+ color = get_theme().COLOR_CHAR_NACK
+ self._win.attron(to_curses_attr(color))
+ self.addstr(get_theme().CHAR_NACK)
+ self._win.attroff(to_curses_attr(color))
+ self.addstr(' ')
+
def write_nickname(self, nickname, color, highlight=False):
"""
Write the nickname, using the user's color
@@ -371,53 +461,19 @@ class TextWin(Win):
color = hl_color
if color:
self._win.attron(to_curses_attr(color))
- self.addstr(truncate_nick(nickname))
+ self.addstr(nickname)
if color:
self._win.attroff(to_curses_attr(color))
if highlight and hl_color == "reverse":
self._win.attroff(curses.A_REVERSE)
- def write_time(self, time):
- """
- Write the date on the yth line of the window
- """
- if time:
- self.addstr(time)
- self.addstr(' ')
-
- def resize(self, height, width, y, x, room=None):
- if hasattr(self, 'width'):
- old_width = self.width
- else:
- old_width = None
- self._resize(height, width, y, x)
- if room and self.width != old_width:
- self.rebuild_everything(room)
-
- # reposition the scrolling after resize
- # (see #2450)
- buf_size = len(self.built_lines)
- if buf_size - self.pos < self.height:
- self.pos = buf_size - self.height
- if self.pos < 0:
- self.pos = 0
-
- def rebuild_everything(self, room):
- self.built_lines = []
- with_timestamps = config.get('show_timestamps')
- for message in room.messages:
- self.build_new_message(message, clean=False, timestamp=with_timestamps)
- if self.separator_after is message:
- self.build_new_message(None)
- while len(self.built_lines) > self.lines_nb_limit:
- self.built_lines.pop(0)
-
def modify_message(self, old_id, message):
"""
Find a message, and replace it with a new one
(instead of rebuilding everything in order to correct a message)
"""
with_timestamps = config.get('show_timestamps')
+ nick_size = config.get('max_nick_length')
for i in range(len(self.built_lines)-1, -1, -1):
if self.built_lines[i] and self.built_lines[i].msg.identifier == old_id:
index = i
@@ -425,7 +481,7 @@ class TextWin(Win):
self.built_lines.pop(index)
index -= 1
index += 1
- lines = self.build_message(message, timestamp=with_timestamps)
+ lines = self.build_message(message, timestamp=with_timestamps, nick_size=nick_size)
for line in lines:
self.built_lines.insert(index, line)
index += 1
@@ -435,3 +491,86 @@ class TextWin(Win):
log.debug('** TextWin: deleting %s built lines', (len(self.built_lines)))
del self.built_lines
+class XMLTextWin(BaseTextWin):
+ def __init__(self):
+ BaseTextWin.__init__(self)
+
+ def refresh(self):
+ log.debug('Refresh: %s', self.__class__.__name__)
+ theme = get_theme()
+ if self.height <= 0:
+ return
+ if self.pos == 0:
+ lines = self.built_lines[-self.height:]
+ else:
+ lines = self.built_lines[-self.height-self.pos:-self.pos]
+ self._win.move(0, 0)
+ self._win.erase()
+ for y, line in enumerate(lines):
+ if line:
+ msg = line.msg
+ if line.start_pos == 0:
+ if msg.nickname == theme.CHAR_XML_OUT:
+ color = theme.COLOR_XML_OUT
+ elif msg.nickname == theme.CHAR_XML_IN:
+ color = theme.COLOR_XML_IN
+ self.write_time(msg.str_time)
+ self.write_prefix(msg.nickname, color)
+ self.addstr(' ')
+ if y != self.height-1:
+ self.addstr('\n')
+ self._win.attrset(0)
+ for y, line in enumerate(lines):
+ offset = 0
+ # Offset for the timestamp (if any) plus a space after it
+ offset += len(line.msg.str_time)
+ # space
+ offset += 1
+
+ # Offset for the prefix
+ offset += poopt.wcswidth(truncate_nick(line.msg.nickname))
+ # space
+ offset += 1
+
+ self.write_text(y, offset,
+ line.prepend+line.msg.txt[line.start_pos:line.end_pos])
+ if y != self.height-1:
+ self.addstr('\n')
+ self._win.attrset(0)
+ self._refresh()
+
+ def build_message(self, message, timestamp=False, nick_size=10):
+ txt = message.txt
+ ret = []
+ default_color = None
+ nick = truncate_nick(message.nickname, nick_size)
+ offset = 0
+ if nick:
+ offset += poopt.wcswidth(nick) + 1 # + nick + ' ' length
+ if message.str_time:
+ offset += 1 + len(message.str_time)
+ if get_theme().CHAR_TIME_LEFT and message.str_time:
+ offset += 1
+ if get_theme().CHAR_TIME_RIGHT and message.str_time:
+ offset += 1
+ lines = poopt.cut_text(txt, self.width-offset-1)
+ prepend = default_color if default_color else ''
+ attrs = []
+ for line in lines:
+ saved = Line(msg=message, start_pos=line[0], end_pos=line[1], prepend=prepend)
+ attrs = parse_attrs(message.txt[line[0]:line[1]], attrs)
+ if attrs:
+ prepend = FORMAT_CHAR + FORMAT_CHAR.join(attrs)
+ else:
+ if default_color:
+ prepend = default_color
+ else:
+ prepend = ''
+ ret.append(saved)
+ return ret
+
+ def write_prefix(self, nickname, color):
+ self._win.attron(to_curses_attr(color))
+ self.addstr(truncate_nick(nickname))
+ self._win.attroff(to_curses_attr(color))
+