diff options
Diffstat (limited to 'src/windows')
-rw-r--r-- | src/windows/__init__.py | 5 | ||||
-rw-r--r-- | src/windows/bookmark_forms.py | 278 | ||||
-rw-r--r-- | src/windows/data_forms.py | 1 | ||||
-rw-r--r-- | src/windows/funcs.py | 4 | ||||
-rw-r--r-- | src/windows/info_bar.py | 7 | ||||
-rw-r--r-- | src/windows/info_wins.py | 14 | ||||
-rw-r--r-- | src/windows/input_placeholders.py | 16 | ||||
-rw-r--r-- | src/windows/inputs.py | 7 | ||||
-rw-r--r-- | src/windows/muc.py | 9 | ||||
-rw-r--r-- | src/windows/roster_win.py | 19 | ||||
-rw-r--r-- | src/windows/text_win.py | 309 |
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)) + |