diff options
Diffstat (limited to 'src/buffers.py')
-rw-r--r-- | src/buffers.py | 1328 |
1 files changed, 0 insertions, 1328 deletions
diff --git a/src/buffers.py b/src/buffers.py deleted file mode 100644 index 74834834..00000000 --- a/src/buffers.py +++ /dev/null @@ -1,1328 +0,0 @@ -# Copyright 2010 Le Coz Florent <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 GNU General Public License as published by -# the Free Software Foundation, version 3 of the License. -# -# Poezio is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Poezio. If not, see <http://www.gnu.org/licenses/>. - -""" -Define all the buffers. -A buffer is a little part of the screen, for example the input buffer, -the text bufferr, the roster buffer, etc. -A Tab (see tab.py) is composed of multiple Buffers -A buffer can also be called Window, even if it's not prefered. -""" - -from gettext import (bindtextdomain, textdomain, bind_textdomain_codeset, - gettext as _) -from os.path import isfile - -import logging -log = logging.getLogger(__name__) - -import locale -locale.setlocale(locale.LC_ALL, '') - -import shlex -import curses -from config import config - -from threading import Lock - -from contact import Contact, Resource -from roster import RosterGroup, roster - -from message import Line -from tab import MIN_WIDTH, MIN_HEIGHT - -from sleekxmpp.xmlstream.stanzabase import JID - -import theme - -g_lock = Lock() - -class Win(object): - def __init__(self, height, width, y, x, parent_win): - self._resize(height, width, y, x, parent_win, True) - - def _resize(self, height, width, y, x, parent_win, visible): - if not visible: - return - self.height, self.width, self.x, self.y = height, width, x, y - # try: - self._win = curses.newwin(height, width, y, x) - # except: - # # When resizing in a too little height (less than 3 lines) - # # We don't need to resize the window, since this size - # # just makes no sense - # # Just don't crash when this happens. - # # (°> also, a penguin - # # //\ - # # V_/_ - # return - - def _refresh(self): - self._win.noutrefresh() - - def addnstr(self, *args): - """ - Safe call to addnstr - """ - try: - self._win.addnstr(*args) - except: - pass - - def addstr(self, *args): - """ - Safe call to addstr - """ - try: - self._win.addstr(*args) - except: - pass - - def finish_line(self, color): - """ - Write colored spaces until the end of line - """ - (y, x) = self._win.getyx() - size = self.width-x - self.addnstr(' '*size, size, curses.color_pair(color)) - -class UserList(Win): - def __init__(self, height, width, y, x, parent_win, visible): - Win.__init__(self, height, width, y, x, parent_win) - self.visible = visible - self.color_role = {'moderator': theme.COLOR_USER_MODERATOR, - 'participant':theme.COLOR_USER_PARTICIPANT, - 'visitor':theme.COLOR_USER_VISITOR, - 'none':theme.COLOR_USER_NONE, - '':theme.COLOR_USER_NONE - } - self.color_show = {'xa':theme.COLOR_STATUS_XA, - 'none':theme.COLOR_STATUS_NONE, - '':theme.COLOR_STATUS_NONE, - 'dnd':theme.COLOR_STATUS_DND, - 'away':theme.COLOR_STATUS_AWAY, - 'chat':theme.COLOR_STATUS_CHAT - } - - def refresh(self, users): - if not self.visible: - return - with g_lock: - self._win.erase() - y = 0 - for user in sorted(users): - if not user.role in self.color_role: - role_col = theme.COLOR_USER_NONE - else: - role_col = self.color_role[user.role] - if not user.show in self.color_show: - show_col = theme.COLOR_STATUS_NONE - else: - show_col = self.color_show[user.show] - self.addstr(y, 0, theme.CHAR_STATUS, curses.color_pair(show_col)) - self.addnstr(y, 1, user.nick, self.width-1, curses.color_pair(role_col)) - y += 1 - if y == self.height: - break - self._refresh() - - def resize(self, height, width, y, x, stdscr, visible): - self.visible = visible - if not visible: - return - self._resize(height, width, y, x, stdscr, visible) - self._win.attron(curses.color_pair(theme.COLOR_VERTICAL_SEPARATOR)) - self._win.vline(0, 0, curses.ACS_VLINE, self.height) - self._win.attroff(curses.color_pair(theme.COLOR_VERTICAL_SEPARATOR)) - -class Topic(Win): - def __init__(self, height, width, y, x, parent_win, visible): - self.visible = visible - Win.__init__(self, height, width, y, x, parent_win) - - def resize(self, height, width, y, x, stdscr, visible): - self._resize(height, width, y, x, stdscr, visible) - - def refresh(self, topic): - if not self.visible: - return - with g_lock: - self._win.erase() - self.addstr(0, 0, topic[:self.width-1], curses.color_pair(theme.COLOR_TOPIC_BAR)) - (y, x) = self._win.getyx() - remaining_size = self.width - x - if remaining_size: - self.addnstr(' '*remaining_size, remaining_size, - curses.color_pair(theme.COLOR_INFORMATION_BAR)) - self._refresh() - -class GlobalInfoBar(Win): - def __init__(self, height, width, y, x, parent_win, visible): - self.visible = visible - Win.__init__(self, height, width, y, x, parent_win) - - def resize(self, height, width, y, x, stdscr, visible): - self._resize(height, width, y, x, stdscr, visible) - - def refresh(self, tabs, current): - if not self.visible: - return - def compare_room(a): - # return a.nb - b.nb - return a.nb - comp = lambda x: x.nb - with g_lock: - self._win.erase() - self.addstr(0, 0, "[", curses.color_pair(theme.COLOR_INFORMATION_BAR)) - sorted_tabs = sorted(tabs, key=comp) - for tab in sorted_tabs: - color = tab.get_color_state() - try: - self.addstr("%s" % str(tab.nb), curses.color_pair(color)) - self.addstr("|", curses.color_pair(theme.COLOR_INFORMATION_BAR)) - except: # end of line - break - (y, x) = self._win.getyx() - self.addstr(y, x-1, '] ', curses.color_pair(theme.COLOR_INFORMATION_BAR)) - (y, x) = self._win.getyx() - remaining_size = self.width - x - self.addnstr(' '*remaining_size, remaining_size, - curses.color_pair(theme.COLOR_INFORMATION_BAR)) - self._refresh() - -class InfoWin(Win): - """ - Base class for all the *InfoWin, used in various tabs. For example - MucInfoWin, etc. Provides some useful methods. - """ - def __init__(self, height, width, y, x, parent_win, visible): - self.visible = visible - Win.__init__(self, height, width, y, x, parent_win) - - def print_scroll_position(self, text_buffer): - """ - Print, link in Weechat, a -PLUS(n)- where n - is the number of available lines to scroll - down - """ - if text_buffer.pos > 0: - plus = ' -PLUS(%s)-' % text_buffer.pos - self.addstr(plus, curses.color_pair(theme.COLOR_SCROLLABLE_NUMBER) | curses.A_BOLD) - -class PrivateInfoWin(InfoWin): - """ - The live above the information window, displaying informations - about the MUC user we are talking to - """ - def __init__(self, height, width, y, x, parent_win, visible): - InfoWin.__init__(self, height, width, y, x, parent_win, visible) - - def resize(self, height, width, y, x, stdscr, visible): - self._resize(height, width, y, x, stdscr, visible) - - def refresh(self, room): - if not self.visible: - return - with g_lock: - self._win.erase() - self.write_room_name(room) - self.print_scroll_position(room) - self.finish_line(theme.COLOR_INFORMATION_BAR) - self._refresh() - - def write_room_name(self, room): - (room_name, nick) = room.name.split('/', 1) - self.addstr(nick, curses.color_pair(13)) - txt = ' from room %s' % room_name - self.addstr(txt, curses.color_pair(theme.COLOR_INFORMATION_BAR)) - -class ConversationInfoWin(InfoWin): - """ - The line above the information window, displaying informations - about the user we are talking to - """ - color_show = {'xa':theme.COLOR_STATUS_XA, - 'none':theme.COLOR_STATUS_ONLINE, - '':theme.COLOR_STATUS_ONLINE, - 'available':theme.COLOR_STATUS_ONLINE, - 'dnd':theme.COLOR_STATUS_DND, - 'away':theme.COLOR_STATUS_AWAY, - 'chat':theme.COLOR_STATUS_CHAT, - 'unavailable':theme.COLOR_STATUS_UNAVAILABLE - } - - def __init__(self, height, width, y, x, parent_win, visible): - InfoWin.__init__(self, height, width, y, x, parent_win, visible) - - def resize(self, height, width, y, x, stdscr, visible): - self._resize(height, width, y, x, stdscr, visible) - - def refresh(self, jid, contact, text_buffer): - if not self.visible: - return - # contact can be None, if we receive a message - # from someone not in our roster. In this case, we display - # only the maximum information from the message we can get. - jid = JID(jid) - if contact: - if jid.resource: - resource = contact.get_resource_by_fulljid(jid.full) - else: - resource = contact.get_highest_priority_resource() - else: - resource = None - # if contact is None, then resource is None too: user is not in the roster - # so we don't know almost anything about it - # If contact is a Contact, then - # resource can now be a Resource: user is in the roster and online - # or resource is None: user is in the roster but offline - with g_lock: - self._win.erase() - self.write_contact_jid(jid) - self.write_contact_informations(contact) - self.write_resource_information(resource) - self.print_scroll_position(text_buffer) - self.finish_line(theme.COLOR_INFORMATION_BAR) - self._refresh() - - def write_resource_information(self, resource): - """ - Write the informations about the resource - """ - if not resource: - presence = "unavailable" - else: - presence = resource.get_presence() - color = RosterWin.color_show[presence] - self.addstr('[', curses.color_pair(theme.COLOR_INFORMATION_BAR)) - self.addstr(" ", curses.color_pair(color)) - self.addstr(']', curses.color_pair(theme.COLOR_INFORMATION_BAR)) - - def write_contact_informations(self, contact): - """ - Write the informations about the contact - """ - if not contact: - self.addstr("(contact not in roster)", curses.color_pair(theme.COLOR_INFORMATION_BAR)) - return - display_name = contact.get_name() or contact.get_bare_jid() - self.addstr('%s '%(display_name), curses.color_pair(theme.COLOR_INFORMATION_BAR)) - - def write_contact_jid(self, jid): - """ - Just write the jid that we are talking to - """ - self.addstr('[', curses.color_pair(theme.COLOR_INFORMATION_BAR)) - self.addstr(jid.full, curses.color_pair(10)) - self.addstr('] ', curses.color_pair(theme.COLOR_INFORMATION_BAR)) - -class ConversationStatusMessageWin(InfoWin): - """ - The upper bar displaying the status message of the contact - """ - def __init__(self, height, width, y, x, parent_win, visible): - InfoWin.__init__(self, height, width, y, x, parent_win, visible) - - def resize(self, height, width, y, x, stdscr, visible): - self._resize(height, width, y, x, stdscr, visible) - - def refresh(self, jid, contact): - if not self.visible: - return - jid = JID(jid) - if contact: - if jid.resource: - resource = contact.get_resource_by_fulljid(jid.full) - else: - resource = contact.get_highest_priority_resource() - else: - resource = None - with g_lock: - self._win.erase() - if resource: - self.write_status_message(resource) - self.finish_line(theme.COLOR_INFORMATION_BAR) - self._refresh() - - def write_status_message(self, resource): - self.addstr(resource.get_status(), curses.color_pair(theme.COLOR_INFORMATION_BAR)) - -class MucInfoWin(InfoWin): - """ - The line just above the information window, displaying informations - about the MUC we are viewing - """ - def __init__(self, height, width, y, x, parent_win, visible): - InfoWin.__init__(self, height, width, y, x, parent_win, visible) - - def resize(self, height, width, y, x, stdscr, visible): - self._resize(height, width, y, x, stdscr, visible) - - def refresh(self, room): - if not self.visible: - return - with g_lock: - self._win.erase() - self.write_room_name(room) - self.write_own_nick(room) - self.write_disconnected(room) - self.write_role(room) - self.print_scroll_position(room) - self.finish_line(theme.COLOR_INFORMATION_BAR) - self._refresh() - - def write_room_name(self, room): - """ - """ - self.addstr('[', curses.color_pair(theme.COLOR_INFORMATION_BAR)) - self.addnstr(room.name, len(room.name), curses.color_pair(13)) - self.addstr('] ', curses.color_pair(theme.COLOR_INFORMATION_BAR)) - - def write_disconnected(self, room): - """ - Shows a message if the room is not joined - """ - if not room.joined: - self.addstr(' -!- Not connected ', curses.color_pair(theme.COLOR_INFORMATION_BAR)) - - def write_own_nick(self, room): - """ - Write our own nick in the info bar - """ - nick = room.own_nick - if not nick: - return - if len(nick) > 13: - nick = nick[:13]+'…' - self.addstr(nick, curses.color_pair(theme.COLOR_INFORMATION_BAR)) - - def write_role(self, room): - """ - Write our own role and affiliation - """ - own_user = None - for user in room.users: - if user.nick == room.own_nick: - own_user = user - break - if not own_user: - return - txt = ' (' - if own_user.affiliation != 'none': - txt += own_user.affiliation+', ' - txt += own_user.role+')' - self.addstr(txt, curses.color_pair(theme.COLOR_INFORMATION_BAR)) - -class TextWin(Win): - """ - Just keep ONE single window for the text area and rewrite EVERYTHING - on each change. (thanks weechat :o) - """ - def __init__(self, height, width, y, x, parent_win, visible): - Win.__init__(self, height, width, y, x, parent_win) - self.visible = visible - - def build_lines_from_messages(self, messages): - """ - From all the existing messages in the window, create the that will - be displayed on the screen - """ - lines = [] - for message in messages: - if message == None: # line separator - lines.append(None) - continue - txt = message.txt - if not txt: - continue - # length of the time - offset = 9+len(theme.CHAR_TIME_LEFT[:1])+len(theme.CHAR_TIME_RIGHT[:1]) - if message.nickname and len(message.nickname) >= 30: - nick = message.nickname[:30]+'…' - else: - nick = message.nickname - if nick: - offset += len(nick) + 2 # + nick + spaces length - first = True - this_line_was_broken_by_space = False - while txt != '': - if txt[:self.width-offset].find('\n') != -1: - limit = txt[:self.width-offset].find('\n') - else: - # break between words if possible - if len(txt) >= self.width-offset: - limit = txt[:self.width-offset].rfind(' ') - this_line_was_broken_by_space = True - if limit <= 0: - limit = self.width-offset - this_line_was_broken_by_space = False - else: - limit = self.width-offset-1 - this_line_was_broken_by_space = False - color = message.user.color if message.user else None - if not first: - nick = None - time = None - else: - time = message.time - l = Line(nick, color, - time, - txt[:limit], message.color, - offset, - message.colorized) - lines.append(l) - if this_line_was_broken_by_space: - txt = txt[limit+1:] # jump the space at the start of the line - else: - txt = txt[limit:] - if txt.startswith('\n'): - txt = txt[1:] - first = False - return lines - return lines[-len(messages):] # return only the needed number of lines - - def refresh(self, room): - """ - Build the Line objects from the messages, and then write - them in the text area - """ - if not self.visible: - return - if self.height <= 0: - return - with g_lock: - self._win.erase() - lines = self.build_lines_from_messages(room.messages) - if room.pos + self.height > len(lines): - room.pos = len(lines) - self.height - if room.pos < 0: - room.pos = 0 - if room.pos != 0: - lines = lines[-self.height-room.pos:-room.pos] - else: - lines = lines[-self.height:] - y = 0 - for line in lines: - self._win.move(y, 0) - if line == None: - self.write_line_separator() - y += 1 - continue - if line.time is not None: - self.write_time(line.time) - if line.nickname is not None: - self.write_nickname(line.nickname, line.nickname_color) - self.write_text(y, line.text_offset, line.text, line.text_color, line.colorized) - y += 1 - self._refresh() - - def write_line_separator(self): - """ - """ - self._win.attron(curses.color_pair(theme.COLOR_NEW_TEXT_SEPARATOR)) - self.addnstr('- '*(self.width//2), self.width) - self._win.attroff(curses.color_pair(theme.COLOR_NEW_TEXT_SEPARATOR)) - - def write_text(self, y, x, txt, color, colorized): - """ - write the text of a line. - """ - txt = txt - if not colorized: - if color: - self._win.attron(curses.color_pair(color)) - self.addstr(y, x, txt) - if color: - self._win.attroff(curses.color_pair(color)) - - else: # Special messages like join or quit - special_words = { - theme.CHAR_JOIN: theme.COLOR_JOIN_CHAR, - theme.CHAR_QUIT: theme.COLOR_QUIT_CHAR, - theme.CHAR_KICK: theme.COLOR_KICK_CHAR, - } - try: - splitted = shlex.split(txt) - except ValueError: - # FIXME colors are disabled on too long words - txt = txt.replace('"[', '').replace(']"', '')\ - .replace('"{', '').replace('}"', '')\ - .replace('"(', '').replace(')"', '') - splitted = txt.split() - for word in splitted: - if word in list(special_words.keys()): - self.addstr(word, curses.color_pair(special_words[word])) - elif word.startswith('(') and word.endswith(')'): - self.addstr('(', curses.color_pair(color)) - self.addstr(word[1:-1], curses.color_pair(theme.COLOR_CURLYBRACKETED_WORD)) - self.addstr(')', curses.color_pair(color)) - elif word.startswith('{') and word.endswith('}'): - self.addstr(word[1:-1], curses.color_pair(theme.COLOR_ACCOLADE_WORD)) - elif word.startswith('[') and word.endswith(']'): - self.addstr(word[1:-1], curses.color_pair(theme.COLOR_BRACKETED_WORD)) - else: - self.addstr(word, curses.color_pair(color)) - self._win.addch(' ') - - def write_nickname(self, nickname, color): - """ - Write the nickname, using the user's color - and return the number of written characters - """ - if color: - self._win.attron(curses.color_pair(color)) - self.addstr(nickname) - if color: - self._win.attroff(curses.color_pair(color)) - self.addstr("> ") - - def write_time(self, time): - """ - Write the date on the yth line of the window - """ - self.addstr(theme.CHAR_TIME_LEFT, curses.color_pair(theme.COLOR_TIME_LIMITER)) - self.addstr(time.strftime("%H"), curses.color_pair(theme.COLOR_TIME_NUMBERS)) - self.addstr(':', curses.color_pair(theme.COLOR_TIME_SEPARATOR)) - self.addstr(time.strftime("%M"), curses.color_pair(theme.COLOR_TIME_NUMBERS)) - self.addstr(':', curses.color_pair(theme.COLOR_TIME_SEPARATOR)) - self.addstr(time.strftime('%S'), curses.color_pair(theme.COLOR_TIME_NUMBERS)) - self.addnstr(theme.CHAR_TIME_RIGHT, curses.color_pair(theme.COLOR_TIME_LIMITER)) - self.addstr(' ') - - def resize(self, height, width, y, x, stdscr, visible): - self.visible = visible - self._resize(height, width, y, x, stdscr, visible) - -class HelpText(Win): - """ - A buffer just displaying a read-only message. - Usually used to replace an Input when the tab is in - command mode. - """ - def __init__(self, height, width, y, x, parent_win, visible, text=''): - self.visible = visible - Win.__init__(self, height, width, y, x, parent_win) - self.txt = text - - def resize(self, height, width, y, x, stdscr, visible): - self._resize(height, width, y, x, stdscr, visible) - - def refresh(self): - if not self.visible: - return - with g_lock: - self._win.erase() - self.addstr(0, 0, self.txt[:self.width-1], curses.color_pair(theme.COLOR_INFORMATION_BAR)) - self.finish_line(theme.COLOR_INFORMATION_BAR) - self._refresh() - - def do_command(self, key): - return False - -class Input(Win): - """ - The simplest Input possible, provides just a way to edit a single line - of text. It also has a clipboard, common to all Inputs. - Doesn't have any history. - It doesn't do anything when enter is pressed either. - This should be herited for all kinds of Inputs, for example MessageInput - or the little inputs in dataforms, etc, adding specific features (completion etc) - It features two kinds of completion, but they have to be called from outside (the Tab), - passing the list of items that can be used to complete. The completion can be used - in a very flexible way. - """ - clipboard = '' # A common clipboard for all the inputs, this makes - # it easy cut and paste text between various input - def __init__(self, height, width, y, x, stdscr, visible): - self.key_func = { - "KEY_LEFT": self.key_left, - "M-D": self.key_left, - "KEY_RIGHT": self.key_right, - "M-C": self.key_right, - "KEY_END": self.key_end, - "KEY_HOME": self.key_home, - "KEY_DC": self.key_dc, - '^D': self.key_dc, - 'M-b': self.jump_word_left, - '^W': self.delete_word, - '^K': self.delete_end_of_line, - '^U': self.delete_begining_of_line, - '^Y': self.paste_clipboard, - '^A': self.key_home, - '^E': self.key_end, - 'M-f': self.jump_word_right, - "KEY_BACKSPACE": self.key_backspace, - '^?': self.key_backspace, - } - - Win.__init__(self, height, width, y, x, stdscr) - self.visible = visible - self.text = '' - self.pos = 0 # cursor position - self.line_pos = 0 # position (in self.text) of - - def is_empty(self): - return len(self.text) == 0 - - def resize(self, height, width, y, x, stdscr, visible): - self.visible = visible - if not visible: - return - self._resize(height, width, y, x, stdscr, visible) - self._win.erase() - self.addnstr(0, 0, self.text, self.width-1) - - def jump_word_left(self): - """ - Move the cursor one word to the left - """ - if not len(self.text) or self.pos == 0: - return - previous_space = self.text[:self.pos+self.line_pos].rfind(' ') - if previous_space == -1: - previous_space = 0 - diff = self.pos+self.line_pos-previous_space - for i in range(diff): - self.key_left() - return True - - def jump_word_right(self): - """ - Move the cursor one word to the right - """ - if len(self.text) == self.pos+self.line_pos or not len(self.text): - return - next_space = self.text.find(' ', self.pos+self.line_pos+1) - if next_space == -1: - next_space = len(self.text) - diff = next_space - (self.pos+self.line_pos) - for i in range(diff): - self.key_right() - return True - - def delete_word(self): - """ - Delete the word just before the cursor - """ - if not len(self.text) or self.pos == 0: - return - previous_space = self.text[:self.pos+self.line_pos].rfind(' ') - if previous_space == -1: - previous_space = 0 - diff = self.pos+self.line_pos-previous_space - for i in range(diff): - self.key_backspace(False) - self.rewrite_text() - return True - - def delete_end_of_line(self): - """ - Cut the text from cursor to the end of line - """ - if len(self.text) == self.pos+self.line_pos: - return # nothing to cut - Input.clipboard = self.text[self.pos+self.line_pos:] - self.text = self.text[:self.pos+self.line_pos] - self.key_end() - return True - - def delete_begining_of_line(self): - """ - Cut the text from cursor to the begining of line - """ - if self.pos+self.line_pos == 0: - return - Input.clipboard = self.text[:self.pos+self.line_pos] - self.text = self.text[self.pos+self.line_pos:] - self.key_home() - return True - - def paste_clipboard(self): - """ - Insert what is in the clipboard at the cursor position - """ - if not Input.clipboard or len(Input.clipboard) == 0: - return - for letter in Input.clipboard: - self.do_command(letter) - return True - - def key_dc(self): - """ - delete char just after the cursor - """ - self.reset_completion() - if self.pos + self.line_pos == len(self.text): - return # end of line, nothing to delete - self.text = self.text[:self.pos+self.line_pos]+self.text[self.pos+self.line_pos+1:] - self.rewrite_text() - return True - - def key_home(self): - """ - Go to the begining of line - """ - self.reset_completion() - self.pos = 0 - self.line_pos = 0 - self.rewrite_text() - return True - - def key_end(self, reset=False): - """ - Go to the end of line - """ - if reset: - self.reset_completion() - if len(self.text) >= self.width-1: - self.pos = self.width-1 - self.line_pos = len(self.text)-self.pos - else: - self.pos = len(self.text) - self.line_pos = 0 - self.rewrite_text() - return True - - def key_left(self): - """ - Move the cursor one char to the left - """ - self.reset_completion() - (y, x) = self._win.getyx() - if self.pos == self.width-1 and self.line_pos > 0: - self.line_pos -= 1 - elif self.pos >= 1: - self.pos -= 1 - self.rewrite_text() - return True - - def key_right(self): - """ - Move the cursor one char to the right - """ - self.reset_completion() - (y, x) = self._win.getyx() - if self.pos == self.width-1: - if self.line_pos + self.width-1 < len(self.text): - self.line_pos += 1 - elif self.pos < len(self.text): - self.pos += 1 - self.rewrite_text() - return True - - def key_backspace(self, reset=True): - """ - Delete the char just before the cursor - """ - self.reset_completion() - (y, x) = self._win.getyx() - if self.pos == 0: - return - self.text = self.text[:self.pos+self.line_pos-1]+self.text[self.pos+self.line_pos:] - self.key_left() - if reset: - self.rewrite_text() - return True - - def auto_completion(self, user_list, add_after=True): - """ - Complete the nickname - """ - if self.pos+self.line_pos != len(self.text): # or len(self.text) == 0 - return # we don't complete if cursor is not at the end of line - completion_type = config.get('completion', 'normal') - if completion_type == 'shell' and self.text != '': - self.shell_completion(user_list, add_after) - else: - self.normal_completion(user_list, add_after) - return True - - def reset_completion(self): - """ - Reset the completion list (called on ALL keys except tab) - """ - self.hit_list = [] - self.last_completion = None - - def normal_completion(self, user_list, add_after): - """ - Normal completion - """ - if add_after and (" " not in self.text.strip() or\ - self.last_completion and self.text == self.last_completion+config.get('after_completion', ',')+" "): - after = config.get('after_completion', ',')+" " - #if " " in self.text.strip() and (not self.last_completion or ' ' in self.last_completion): - else: - after = " " # don't put the "," if it's not the begining of the sentence - (y, x) = self._win.getyx() - if not self.last_completion: - # begin is the begining of the nick we want to complete - if self.text.strip() != '': - begin = self.text.split()[-1].lower() - else: - begin = '' - hit_list = [] # list of matching nicks - for user in user_list: - if user.lower().startswith(begin): - hit_list.append(user) - if len(hit_list) == 0: - return - self.hit_list = hit_list - end = len(begin) - else: - begin = self.text[-len(after)-len(self.last_completion):-len(after)] - self.hit_list.append(self.hit_list.pop(0)) # rotate list - end = len(begin) + len(after) - self.text = self.text[:-end] - nick = self.hit_list[0] # take the first hit - self.last_completion = nick - self.text += nick +after - self.key_end(False) - - def shell_completion(self, user_list, add_after): - """ - Shell-like completion - """ - if " " in self.text.strip() or not add_after: - after = " " # don't put the "," if it's not the begining of the sentence - else: - after = config.get('after_completion', ',')+" " - (y, x) = self._win.getyx() - if self.text != '': - begin = self.text.split()[-1].lower() - else: - begin = '' - hit_list = [] # list of matching nicks - for user in user_list: - if user.lower().startswith(begin): - hit_list.append(user) - if len(hit_list) == 0: - return - end = False - nick = '' - last_completion = self.last_completion - self.last_completion = True - if len(hit_list) == 1: - nick = hit_list[0] + after - self.last_completion = False - elif last_completion: - for n in hit_list: - if begin.lower() == n.lower(): - nick = n+after # user DO want this completion (tabbed twice on it) - self.last_completion = False - if nick == '': - while not end and len(nick) < len(hit_list[0]): - nick = hit_list[0][:len(nick)+1] - for hit in hit_list: - if not hit.lower().startswith(nick.lower()): - end = True - break - if end: - nick = nick[:-1] - x -= len(begin) - self.text = self.text[:-len(begin)] - self.text += nick - self.key_end(False) - - def do_command(self, key, reset=True): - if key in self.key_func: - return self.key_func[key]() - if not key or len(key) > 1: - return False # ignore non-handled keyboard shortcuts - self.reset_completion() - self.text = self.text[:self.pos+self.line_pos]+key+self.text[self.pos+self.line_pos:] - (y, x) = self._win.getyx() - if x == self.width-1: - self.line_pos += 1 - else: - self.pos += 1 - if reset: - self.rewrite_text() - return True - - def get_text(self): - """ - Clear the input and return the text entered so far - """ - return self.text - - def rewrite_text(self): - """ - Refresh the line onscreen, from the pos and pos_line - """ - with g_lock: - self._win.erase() - self.addstr(self.text[self.line_pos:self.line_pos+self.width-1]) - cursor_pos = self.pos - self.addstr(0, cursor_pos, '') # WTF, this works but .move() doesn't… - self._refresh() - - def refresh(self): - if not self.visible: - return - self.rewrite_text() - - def clear_text(self): - self.text = '' - self.pos = 0 - self.line_pos = 0 - self.rewrite_text() - -class MessageInput(Input): - """ - The input featuring history and that is being used in - Conversation, Muc and Private tabs - """ - history = list() # The history is common to all MessageInput - - def __init__(self, height, width, y, x, stdscr, visible): - Input.__init__(self, height, width, y, x, stdscr, visible) - self.histo_pos = 0 - self.key_func["KEY_UP"] = self.key_up - self.key_func["M-A"] = self.key_up - self.key_func["KEY_DOWN"] = self.key_down - self.key_func["M-B"] = self.key_down - - def key_up(self): - """ - Get the previous line in the history - """ - if not len(MessageInput.history): - return - self.reset_completion() - self._win.erase() - if self.histo_pos >= 0: - self.histo_pos -= 1 - self.text = MessageInput.history[self.histo_pos+1] - self.key_end() - - def key_down(self): - """ - Get the next line in the history - """ - if not len(MessageInput.history): - return - self.reset_completion() - if self.histo_pos < len(MessageInput.history)-1: - self.histo_pos += 1 - self.text = self.history[self.histo_pos] - self.key_end() - else: - self.histo_pos = len(MessageInput.history)-1 - self.text = '' - self.pos = 0 - self.line_pos = 0 - self.rewrite_text() - - def key_enter(self): - txt = self.get_text() - if len(txt) != 0: - self.history.append(txt) - self.histo_pos = len(self.history)-1 - self.clear_text() - return txt - -class CommandInput(Input): - """ - An input with an help message in the left, with three given callbacks: - one when when successfully 'execute' the command and when we abort it. - The last callback is optional and is called on any input key - This input is used, for example, in the RosterTab when, to replace the - HelpMessage when a command is started - The on_input callback - """ - def __init__(self, height, width, y, x, stdscr, visible, - help_message, on_abort, on_success, on_input=None): - Input.__init__(self, height, width, y, x, stdscr, visible) - self.on_abort = on_abort - self.on_success = on_success - self.on_input = on_input - self.help_message = help_message - self.key_func['^J'] = self.success - self.key_func['^M'] = self.success - self.key_func['\n'] = self.success - self.key_func['^G'] = self.abort - - def do_command(self, key): - res = Input.do_command(self, key) - if self.on_input: - self.on_input(self.get_text()) - return res - - def success(self): - """ - call the success callback, passing the text as argument - """ - self.on_input = None - res = self.on_success(self.get_text()) - return res - - def abort(self): - """ - Call the abort callback, passing the text as argument - """ - self.on_input = None - return self.on_abort(self.get_text()) - - def rewrite_text(self): - """ - Rewrite the text just like a normal input, but with the instruction - on the left - """ - with g_lock: - self._win.erase() - self.addstr(self.help_message, curses.color_pair(theme.COLOR_INFORMATION_BAR)) - cursor_pos = self.pos + len(self.help_message) - if len(self.help_message): - self.addstr(' ') - cursor_pos += 1 - self.addstr(self.text[self.line_pos:self.line_pos+self.width-1]) - self.addstr(0, cursor_pos, '') # WTF, this works but .move() doesn't… - self._refresh() - -class VerticalSeparator(Win): - """ - Just a one-column window, with just a line in it, that is - refreshed only on resize, but never on refresh, for efficiency - """ - def __init__(self, height, width, y, x, parent_win, visible): - Win.__init__(self, height, width, y, x, parent_win) - self.visible = visible - - def rewrite_line(self): - with g_lock: - self._win.vline(0, 0, curses.ACS_VLINE, self.height, curses.color_pair(theme.COLOR_VERTICAL_SEPARATOR)) - self._refresh() - - def resize(self, height, width, y, x, stdscr, visible): - self.visible = visible - self._resize(height, width, y, x, stdscr, visible) - if not visible: - return - - def refresh(self): - if not self.visible: - return - self.rewrite_line() - -class RosterWin(Win): - color_show = {'xa':theme.COLOR_STATUS_XA, - 'none':theme.COLOR_STATUS_ONLINE, - '':theme.COLOR_STATUS_ONLINE, - 'available':theme.COLOR_STATUS_ONLINE, - 'dnd':theme.COLOR_STATUS_DND, - 'away':theme.COLOR_STATUS_AWAY, - 'chat':theme.COLOR_STATUS_CHAT, - 'unavailable':theme.COLOR_STATUS_UNAVAILABLE - } - - def __init__(self, height, width, y, x, parent_win, visible): - self.visible = visible - Win.__init__(self, height, width, y, x, parent_win) - self.pos = 0 # cursor position in the contact list - self.start_pos = 1 # position of the start of the display - self.roster_len = 0 - self.selected_row = None - - def resize(self, height, width, y, x, stdscr, visible): - self._resize(height, width, y, x, stdscr, visible) - self.visible = visible - - def move_cursor_down(self): - if self.pos < self.roster_len-1: - self.pos += 1 - if self.pos == self.start_pos-1 + self.height-1: - self.scroll_down() - - def move_cursor_up(self): - if self.pos > 0: - self.pos -= 1 - if self.pos == self.start_pos-2: - self.scroll_up() - - def scroll_down(self): - self.start_pos += 8 - - def scroll_up(self): - self.start_pos -= 8 - - def refresh(self, roster): - """ - We get the roster object - """ - if not self.visible: - return - with g_lock: - self.roster_len = len(roster) - while self.roster_len and self.pos >= self.roster_len: - self.move_cursor_up() - self._win.erase() - self.draw_roster_information(roster) - y = 1 - for group in roster.get_groups(): - if group.get_nb_connected_contacts() == 0: - continue # Ignore empty groups - # This loop is really REALLY ugly :^) - if y-1 == self.pos: - self.selected_row = group - if y >= self.start_pos: - self.draw_group(y-self.start_pos+1, group, y-1==self.pos) - y += 1 - if group.folded: - continue - for contact in group.get_contacts(roster._contact_filter): - if config.get('roster_show_offline', 'false') == 'false' and\ - contact.get_nb_resources() == 0: - continue - if y-1 == self.pos: - self.selected_row = contact - if y-self.start_pos+1 == self.height: - break - if y >= self.start_pos: - self.draw_contact_line(y-self.start_pos+1, contact, y-1==self.pos) - y += 1 - if not contact._folded: - for resource in contact.get_resources(): - if y-1 == self.pos: - self.selected_row = resource - if y-self.start_pos+1 == self.height: - break - if y >= self.start_pos: - self.draw_resource_line(y-self.start_pos+1, resource, y-1==self.pos) - y += 1 - if y-self.start_pos+1 == self.height: - break - if self.start_pos > 1: - self.draw_plus(1) - if self.start_pos + self.height-2 < self.roster_len: - self.draw_plus(self.height-1) - self._refresh() - - def draw_plus(self, y): - """ - Draw the indicator that shows that - the list is longer than what is displayed - """ - self.addstr(y, self.width-5, '++++', curses.color_pair(42)) - - def draw_roster_information(self, roster): - """ - """ - self.addstr('%s contacts' % roster.get_contact_len(), curses.color_pair(12)) - self.finish_line(12) - - def draw_group(self, y, group, colored): - """ - Draw a groupname on a line - """ - if colored: - self._win.attron(curses.color_pair(14)) - if group.folded: - self.addstr(y, 0, '[+] ') - else: - self.addstr(y, 0, '[-] ') - self.addstr(y, 4, group.name) - if colored: - self._win.attroff(curses.color_pair(14)) - - def draw_contact_line(self, y, contact, colored): - """ - Draw on a line all informations about one contact. - This is basically the highest priority resource's informations - Use 'color' to draw the jid/display_name to show what is - the currently selected contact in the list - """ - resource = contact.get_highest_priority_resource() - if not resource: - # There's no online resource - presence = 'unavailable' - folder = ' ' - nb = '' - else: - presence = resource.get_presence() - folder = '[+]' if contact._folded else '[-]' - nb = '(%s)' % (contact.get_nb_resources(),) - color = RosterWin.color_show[presence] - if contact.get_name(): - display_name = '%s (%s) %s' % (contact.get_name(), - contact.get_bare_jid(), nb,) - else: - display_name = '%s %s' % (contact.get_bare_jid(), nb,) - self.addstr(y, 1, " ", curses.color_pair(color)) - if resource: - self.addstr(y, 2, ' [+]' if contact._folded else ' [-]') - self.addstr(' ') - if colored: - self.addstr(display_name, curses.color_pair(14)) - else: - self.addstr(display_name) - - def draw_resource_line(self, y, resource, colored): - """ - Draw a specific resource line - """ - color = RosterWin.color_show[resource.get_presence()] - self.addstr(y, 4, " ", curses.color_pair(color)) - if colored: - self.addstr(y, 6, resource.get_jid().full, curses.color_pair(14)) - else: - self.addstr(y, 6, resource.get_jid().full) - - def get_selected_row(self): - return self.selected_row - -class ContactInfoWin(Win): - def __init__(self, height, width, y, x, parent_win, visible): - self.visible = visible - Win.__init__(self, height, width, y, x, parent_win) - - def resize(self, height, width, y, x, stdscr, visible): - self._resize(height, width, y, x, stdscr, visible) - self.visible = visible - - def draw_contact_info(self, resource, jid=None): - """ - draw the contact information - """ - jid = jid or resource.get_jid().full - if resource: - presence = resource.get_presence() - else: - presence = 'unavailable' - self.addstr(0, 0, jid, curses.color_pair(theme.COLOR_INFORMATION_BAR)) - self.addstr(' (%s)'%(presence,), curses.color_pair(theme.COLOR_INFORMATION_BAR)) - self.finish_line(theme.COLOR_INFORMATION_BAR) - - def draw_group_info(self, group): - """ - draw the group information - """ - self.addstr(0, 0, group.name, curses.color_pair(theme.COLOR_INFORMATION_BAR)) - self.finish_line(theme.COLOR_INFORMATION_BAR) - - def refresh(self, selected_row): - if not self.visible: - return - with g_lock: - self._win.erase() - if isinstance(selected_row, RosterGroup): - self.draw_group_info(selected_row) - elif isinstance(selected_row, Contact): - self.draw_contact_info(selected_row.get_highest_priority_resource(), - selected_row.get_bare_jid()) - elif isinstance(selected_row, Resource): - self.draw_contact_info(selected_row) - self._refresh() |