2010-10-04 01:45:16 +00:00
|
|
|
|
# Copyright 2010 Le Coz Florent <louiz@louiz.org>
|
2010-09-14 02:11:07 +00:00
|
|
|
|
#
|
|
|
|
|
# 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/>.
|
|
|
|
|
|
|
|
|
|
"""
|
2010-11-22 01:39:37 +00:00
|
|
|
|
a Tab object is a way to organize various Windows (see windows.py)
|
2010-09-14 02:11:07 +00:00
|
|
|
|
around the screen at once.
|
2010-11-15 11:59:09 +00:00
|
|
|
|
A tab is then composed of multiple Buffer.
|
|
|
|
|
Each Tab object has different refresh() and resize() methods, defining how its
|
|
|
|
|
Buffer are displayed, resized, etc
|
2010-09-14 02:11:07 +00:00
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
MIN_WIDTH = 50
|
2010-09-14 02:24:04 +00:00
|
|
|
|
MIN_HEIGHT = 16
|
2010-09-14 02:11:07 +00:00
|
|
|
|
|
2010-11-11 03:39:39 +00:00
|
|
|
|
import logging
|
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
2010-11-22 01:39:37 +00:00
|
|
|
|
import windows
|
2010-09-14 02:11:07 +00:00
|
|
|
|
import theme
|
2010-10-17 05:14:22 +00:00
|
|
|
|
import curses
|
2010-11-11 05:24:43 +00:00
|
|
|
|
import difflib
|
|
|
|
|
|
|
|
|
|
from sleekxmpp.xmlstream.stanzabase import JID
|
2010-10-27 22:49:52 +00:00
|
|
|
|
from config import config
|
2010-10-31 18:57:48 +00:00
|
|
|
|
from roster import RosterGroup, roster
|
2010-10-17 17:27:07 +00:00
|
|
|
|
from contact import Contact, Resource
|
2010-09-14 02:11:07 +00:00
|
|
|
|
|
|
|
|
|
class Tab(object):
|
|
|
|
|
number = 0
|
|
|
|
|
|
2010-11-19 16:27:20 +00:00
|
|
|
|
def __init__(self, core):
|
2010-11-09 20:37:39 +00:00
|
|
|
|
self.core = core # a pointer to core, to access its attributes (ugly?)
|
2010-09-14 02:11:07 +00:00
|
|
|
|
self.nb = Tab.number
|
|
|
|
|
Tab.number += 1
|
2010-11-19 16:27:20 +00:00
|
|
|
|
self.size = (self.height, self.width) = self.core.stdscr.getmaxyx()
|
2010-09-14 02:11:07 +00:00
|
|
|
|
if self.height < MIN_HEIGHT or self.width < MIN_WIDTH:
|
|
|
|
|
self.visible = False
|
|
|
|
|
else:
|
|
|
|
|
self.visible = True
|
2010-11-22 02:00:03 +00:00
|
|
|
|
self.key_func = {} # each tab should add their keys in there
|
|
|
|
|
# and use them in on_input
|
2010-09-14 02:11:07 +00:00
|
|
|
|
|
2010-09-26 18:01:38 +00:00
|
|
|
|
def refresh(self, tabs, informations, roster):
|
2010-09-14 02:11:07 +00:00
|
|
|
|
"""
|
|
|
|
|
Called on each screen refresh (when something has changed)
|
|
|
|
|
"""
|
|
|
|
|
raise NotImplementedError
|
|
|
|
|
|
2010-11-19 16:27:20 +00:00
|
|
|
|
def resize(self):
|
|
|
|
|
self.size = (self.height, self.width) = self.core.stdscr.getmaxyx()
|
2010-09-14 02:11:07 +00:00
|
|
|
|
if self.height < MIN_HEIGHT or self.width < MIN_WIDTH:
|
|
|
|
|
self.visible = False
|
|
|
|
|
else:
|
|
|
|
|
self.visible = True
|
|
|
|
|
|
|
|
|
|
def get_color_state(self):
|
|
|
|
|
"""
|
|
|
|
|
returns the color that should be used in the GlobalInfoBar
|
|
|
|
|
"""
|
|
|
|
|
raise NotImplementedError
|
|
|
|
|
|
|
|
|
|
def set_color_state(self, color):
|
|
|
|
|
"""
|
|
|
|
|
set the color state
|
|
|
|
|
"""
|
|
|
|
|
raise NotImplementedError
|
|
|
|
|
|
|
|
|
|
def get_name(self):
|
|
|
|
|
"""
|
|
|
|
|
get the name of the tab
|
|
|
|
|
"""
|
|
|
|
|
raise NotImplementedError
|
|
|
|
|
|
|
|
|
|
def on_input(self, key):
|
|
|
|
|
raise NotImplementedError
|
|
|
|
|
|
|
|
|
|
def on_lose_focus(self):
|
|
|
|
|
"""
|
|
|
|
|
called when this tab loses the focus.
|
|
|
|
|
"""
|
|
|
|
|
raise NotImplementedError
|
|
|
|
|
|
|
|
|
|
def on_gain_focus(self):
|
|
|
|
|
"""
|
|
|
|
|
called when this tab gains the focus.
|
|
|
|
|
"""
|
|
|
|
|
raise NotImplementedError
|
|
|
|
|
|
|
|
|
|
def add_message(self):
|
|
|
|
|
"""
|
|
|
|
|
Adds a message in the tab.
|
|
|
|
|
If the tab cannot add a message in itself (for example
|
|
|
|
|
FormTab, where text is not intented to be appened), it returns False.
|
|
|
|
|
If the tab can, it returns True
|
|
|
|
|
"""
|
|
|
|
|
raise NotImplementedError
|
|
|
|
|
|
|
|
|
|
def on_scroll_down(self):
|
|
|
|
|
"""
|
|
|
|
|
Defines what happens when we scrol down
|
|
|
|
|
"""
|
|
|
|
|
raise NotImplementedError
|
|
|
|
|
|
|
|
|
|
def on_scroll_up(self):
|
|
|
|
|
"""
|
|
|
|
|
Defines what happens when we scrol down
|
|
|
|
|
"""
|
|
|
|
|
raise NotImplementedError
|
|
|
|
|
|
2010-11-19 16:27:20 +00:00
|
|
|
|
def on_info_win_size_changed(self):
|
2010-09-14 02:11:07 +00:00
|
|
|
|
"""
|
|
|
|
|
Called when the window with the informations is resized
|
|
|
|
|
"""
|
|
|
|
|
raise NotImplementedError
|
|
|
|
|
|
2010-10-17 05:14:22 +00:00
|
|
|
|
def just_before_refresh(self):
|
|
|
|
|
"""
|
|
|
|
|
Method called just before the screen refresh.
|
|
|
|
|
Particularly useful to move the cursor at the
|
|
|
|
|
correct position.
|
|
|
|
|
"""
|
|
|
|
|
raise NotImplementedError
|
|
|
|
|
|
2010-11-11 03:39:39 +00:00
|
|
|
|
def on_close(self):
|
|
|
|
|
"""
|
|
|
|
|
Called when the tab is to be closed
|
|
|
|
|
"""
|
|
|
|
|
raise NotImplementedError
|
|
|
|
|
|
2010-09-14 02:11:07 +00:00
|
|
|
|
class InfoTab(Tab):
|
|
|
|
|
"""
|
|
|
|
|
The information tab, used to display global informations
|
|
|
|
|
when using a anonymous account
|
|
|
|
|
"""
|
2010-11-19 16:27:20 +00:00
|
|
|
|
def __init__(self, core, name):
|
|
|
|
|
Tab.__init__(self, core)
|
2010-11-22 02:25:15 +00:00
|
|
|
|
self.tab_win = windows.GlobalInfoBar()
|
|
|
|
|
self.text_win = windows.TextWin()
|
|
|
|
|
self.input = windows.Input()
|
2010-09-14 02:11:07 +00:00
|
|
|
|
self.name = name
|
|
|
|
|
self.color_state = theme.COLOR_TAB_NORMAL
|
2010-11-22 02:25:15 +00:00
|
|
|
|
self.resize()
|
2010-09-14 02:11:07 +00:00
|
|
|
|
|
2010-11-19 16:27:20 +00:00
|
|
|
|
def resize(self):
|
|
|
|
|
Tab.resize(self)
|
2010-11-22 02:39:56 +00:00
|
|
|
|
self.tab_win.resize(1, self.width, self.height-2, 0, self.core.stdscr)
|
|
|
|
|
self.tab_win.resize(1, self.width, self.height-2, 0, self.core.stdscr)
|
|
|
|
|
self.text_win.resize(self.height-2, self.width, 0, 0, self.core.stdscr)
|
|
|
|
|
self.input.resize(1, self.width, self.height-1, 0, self.core.stdscr)
|
2010-09-14 02:11:07 +00:00
|
|
|
|
|
2010-09-26 18:01:38 +00:00
|
|
|
|
def refresh(self, tabs, informations, _):
|
2010-11-22 02:39:56 +00:00
|
|
|
|
if not self.visible:
|
|
|
|
|
return
|
2010-09-14 02:11:07 +00:00
|
|
|
|
self.text_win.refresh(informations)
|
|
|
|
|
self.tab_win.refresh(tabs, tabs[0])
|
|
|
|
|
self.input.refresh()
|
|
|
|
|
|
|
|
|
|
def get_name(self):
|
|
|
|
|
return self.name
|
|
|
|
|
|
|
|
|
|
def get_color_state(self):
|
|
|
|
|
return self.color_state
|
|
|
|
|
|
|
|
|
|
def set_color_state(self, color):
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
def on_input(self, key):
|
|
|
|
|
return self.input.do_command(key)
|
|
|
|
|
|
|
|
|
|
def on_lose_focus(self):
|
|
|
|
|
self.color_state = theme.COLOR_TAB_NORMAL
|
|
|
|
|
|
|
|
|
|
def on_gain_focus(self):
|
|
|
|
|
self.color_state = theme.COLOR_TAB_CURRENT
|
2010-10-17 05:14:22 +00:00
|
|
|
|
curses.curs_set(0)
|
2010-09-14 02:11:07 +00:00
|
|
|
|
|
|
|
|
|
def on_scroll_up(self):
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
def on_scroll_down(self):
|
|
|
|
|
pass
|
|
|
|
|
|
2010-11-19 16:27:20 +00:00
|
|
|
|
def on_info_win_size_changed(self):
|
2010-09-14 02:11:07 +00:00
|
|
|
|
return
|
|
|
|
|
|
2010-10-17 05:14:22 +00:00
|
|
|
|
def just_before_refresh(self):
|
|
|
|
|
return
|
|
|
|
|
|
2010-11-11 03:39:39 +00:00
|
|
|
|
def on_close(self):
|
|
|
|
|
return
|
|
|
|
|
|
2010-11-22 01:39:37 +00:00
|
|
|
|
class ChatTab(Tab):
|
|
|
|
|
"""
|
2010-11-22 02:00:03 +00:00
|
|
|
|
A tab containing a chat of any type.
|
|
|
|
|
Just use this class instead of Tab if the tab needs a recent-words completion
|
|
|
|
|
Also, \n, ^J and ^M are already bound to on_enter
|
2010-11-22 01:39:37 +00:00
|
|
|
|
"""
|
2010-11-22 02:00:03 +00:00
|
|
|
|
def __init__(self, core, room):
|
|
|
|
|
Tab.__init__(self, core)
|
|
|
|
|
self._room = room
|
|
|
|
|
self.key_func['M-/'] = self.last_words_completion
|
|
|
|
|
self.key_func['^J'] = self.on_enter
|
|
|
|
|
self.key_func['^M'] = self.on_enter
|
|
|
|
|
self.key_func['\n'] = self.on_enter
|
|
|
|
|
|
|
|
|
|
def last_words_completion(self):
|
|
|
|
|
"""
|
|
|
|
|
Complete the input with words recently said
|
|
|
|
|
"""
|
|
|
|
|
# build the list of the recent words
|
|
|
|
|
char_we_dont_want = [',', '(', ')', '.', '"', '\'', ' ', # The last one is nbsp
|
|
|
|
|
'’', '“', '”', ':', ';', '[', ']', '{', '}']
|
|
|
|
|
words = list()
|
|
|
|
|
for msg in self._room.messages[:-40:-1]:
|
|
|
|
|
if not msg:
|
|
|
|
|
continue
|
|
|
|
|
for char in char_we_dont_want:
|
|
|
|
|
msg.txt = msg.txt.replace(char, ' ')
|
|
|
|
|
for word in msg.txt.split():
|
|
|
|
|
if len(word) >= 4 and word not in words:
|
|
|
|
|
words.append(word)
|
|
|
|
|
self.input.auto_completion(words, False)
|
|
|
|
|
|
|
|
|
|
def on_enter(self):
|
|
|
|
|
raise NotImplementedError
|
|
|
|
|
|
|
|
|
|
class MucTab(ChatTab):
|
2010-09-14 02:11:07 +00:00
|
|
|
|
"""
|
|
|
|
|
The tab containing a multi-user-chat room.
|
|
|
|
|
It contains an userlist, an input, a topic, an information and a chat zone
|
|
|
|
|
"""
|
2010-11-19 16:27:20 +00:00
|
|
|
|
def __init__(self, core, room):
|
2010-11-22 02:00:03 +00:00
|
|
|
|
ChatTab.__init__(self, core, room)
|
2010-11-22 02:25:15 +00:00
|
|
|
|
self.topic_win = windows.Topic()
|
|
|
|
|
self.text_win = windows.TextWin()
|
|
|
|
|
self.v_separator = windows.VerticalSeparator()
|
|
|
|
|
self.user_win = windows.UserList()
|
|
|
|
|
self.info_header = windows.MucInfoWin()
|
|
|
|
|
self.info_win = windows.TextWin()
|
|
|
|
|
self.tab_win = windows.GlobalInfoBar()
|
|
|
|
|
self.input = windows.MessageInput()
|
2010-11-22 02:00:03 +00:00
|
|
|
|
self.key_func['^I'] = self.completion
|
|
|
|
|
self.key_func['M-i'] = self.completion
|
2010-11-22 02:25:15 +00:00
|
|
|
|
self.resize()
|
2010-09-14 02:11:07 +00:00
|
|
|
|
|
2010-11-19 16:27:20 +00:00
|
|
|
|
def resize(self):
|
2010-09-14 02:11:07 +00:00
|
|
|
|
"""
|
|
|
|
|
Resize the whole window. i.e. all its sub-windows
|
|
|
|
|
"""
|
2010-11-19 16:27:20 +00:00
|
|
|
|
Tab.resize(self)
|
2010-09-14 02:11:07 +00:00
|
|
|
|
text_width = (self.width//10)*9
|
2010-11-22 02:39:56 +00:00
|
|
|
|
self.topic_win.resize(1, self.width, 0, 0, self.core.stdscr)
|
|
|
|
|
self.text_win.resize(self.height-4-self.core.information_win_size, text_width, 1, 0, self.core.stdscr)
|
|
|
|
|
self.v_separator.resize(self.height-3, 1, 1, 9*(self.width//10), self.core.stdscr)
|
|
|
|
|
self.user_win.resize(self.height-3, self.width-text_width-1, 1, text_width+1, self.core.stdscr)
|
|
|
|
|
self.info_header.resize(1, (self.width//10)*9, self.height-3-self.core.information_win_size, 0, self.core.stdscr)
|
|
|
|
|
self.info_win.resize(self.core.information_win_size, (self.width//10)*9, self.height-2-self.core.information_win_size, 0, self.core.stdscr)
|
|
|
|
|
self.tab_win.resize(1, self.width, self.height-2, 0, self.core.stdscr)
|
|
|
|
|
self.input.resize(1, self.width, self.height-1, 0, self.core.stdscr)
|
2010-09-14 02:11:07 +00:00
|
|
|
|
|
2010-09-26 18:01:38 +00:00
|
|
|
|
def refresh(self, tabs, informations, _):
|
2010-11-22 02:39:56 +00:00
|
|
|
|
if not self.visible:
|
|
|
|
|
return
|
2010-09-14 02:11:07 +00:00
|
|
|
|
self.topic_win.refresh(self._room.topic)
|
|
|
|
|
self.text_win.refresh(self._room)
|
|
|
|
|
self.v_separator.refresh()
|
|
|
|
|
self.user_win.refresh(self._room.users)
|
|
|
|
|
self.info_header.refresh(self._room)
|
|
|
|
|
self.info_win.refresh(informations)
|
|
|
|
|
self.tab_win.refresh(tabs, tabs[0])
|
|
|
|
|
self.input.refresh()
|
|
|
|
|
|
|
|
|
|
def on_input(self, key):
|
2010-11-22 02:00:03 +00:00
|
|
|
|
if key in self.key_func:
|
|
|
|
|
self.key_func[key]()
|
2010-11-15 13:16:30 +00:00
|
|
|
|
return False
|
|
|
|
|
self.input.do_command(key)
|
|
|
|
|
return False
|
2010-09-14 02:11:07 +00:00
|
|
|
|
|
|
|
|
|
def completion(self):
|
|
|
|
|
"""
|
|
|
|
|
Called when Tab is pressed, complete the nickname in the input
|
|
|
|
|
"""
|
|
|
|
|
compare_users = lambda x: x.last_talked
|
|
|
|
|
self.input.auto_completion([user.nick for user in sorted(self._room.users, key=compare_users, reverse=True)])
|
|
|
|
|
|
2010-11-11 20:44:14 +00:00
|
|
|
|
def on_enter(self):
|
|
|
|
|
"""
|
|
|
|
|
When enter is pressed, send the message to the Muc
|
|
|
|
|
"""
|
|
|
|
|
self.core.execute(self.input.key_enter())
|
|
|
|
|
|
2010-09-14 02:11:07 +00:00
|
|
|
|
def get_color_state(self):
|
|
|
|
|
return self._room.color_state
|
|
|
|
|
|
|
|
|
|
def set_color_state(self, color):
|
|
|
|
|
self._room.set_color_state(color)
|
|
|
|
|
|
|
|
|
|
def get_name(self):
|
|
|
|
|
return self._room.name
|
|
|
|
|
|
|
|
|
|
def get_room(self):
|
|
|
|
|
return self._room
|
|
|
|
|
|
|
|
|
|
def on_lose_focus(self):
|
|
|
|
|
self._room.set_color_state(theme.COLOR_TAB_NORMAL)
|
|
|
|
|
self._room.remove_line_separator()
|
2010-09-14 22:59:40 +00:00
|
|
|
|
self._room.add_line_separator()
|
2010-09-14 02:11:07 +00:00
|
|
|
|
|
|
|
|
|
def on_gain_focus(self):
|
|
|
|
|
self._room.set_color_state(theme.COLOR_TAB_CURRENT)
|
2010-10-17 05:14:22 +00:00
|
|
|
|
curses.curs_set(1)
|
2010-09-14 02:11:07 +00:00
|
|
|
|
|
|
|
|
|
def on_scroll_up(self):
|
|
|
|
|
self._room.scroll_up(self.text_win.height-1)
|
|
|
|
|
|
|
|
|
|
def on_scroll_down(self):
|
|
|
|
|
self._room.scroll_down(self.text_win.height-1)
|
|
|
|
|
|
2010-11-19 16:27:20 +00:00
|
|
|
|
def on_info_win_size_changed(self):
|
2010-09-14 02:11:07 +00:00
|
|
|
|
text_width = (self.width//10)*9
|
2010-11-22 02:39:56 +00:00
|
|
|
|
self.text_win.resize(self.height-4-self.core.information_win_size, text_width, 1, 0, self.core.stdscr)
|
|
|
|
|
self.info_header.resize(1, (self.width//10)*9, self.height-3-self.core.information_win_size, 0, self.core.stdscr)
|
|
|
|
|
self.info_win.resize(self.core.information_win_size, (self.width//10)*9, self.height-2-self.core.information_win_size, 0, self.core.stdscr)
|
2010-09-14 02:11:07 +00:00
|
|
|
|
|
2010-10-17 05:14:22 +00:00
|
|
|
|
def just_before_refresh(self):
|
2010-11-11 20:44:14 +00:00
|
|
|
|
return
|
2010-10-17 05:14:22 +00:00
|
|
|
|
|
2010-11-11 03:39:39 +00:00
|
|
|
|
def on_close(self):
|
|
|
|
|
return
|
|
|
|
|
|
2010-11-22 02:00:03 +00:00
|
|
|
|
class PrivateTab(ChatTab):
|
2010-09-14 02:11:07 +00:00
|
|
|
|
"""
|
|
|
|
|
The tab containg a private conversation (someone from a MUC)
|
|
|
|
|
"""
|
2010-11-19 16:27:20 +00:00
|
|
|
|
def __init__(self, core, room):
|
2010-11-22 02:00:03 +00:00
|
|
|
|
ChatTab.__init__(self, core, room)
|
2010-11-22 02:25:15 +00:00
|
|
|
|
self.text_win = windows.TextWin()
|
|
|
|
|
self.info_header = windows.PrivateInfoWin()
|
|
|
|
|
self.info_win = windows.TextWin()
|
|
|
|
|
self.tab_win = windows.GlobalInfoBar()
|
|
|
|
|
self.input = windows.MessageInput()
|
|
|
|
|
self.resize()
|
2010-11-19 16:27:20 +00:00
|
|
|
|
|
|
|
|
|
def resize(self):
|
|
|
|
|
Tab.resize(self)
|
2010-11-22 02:39:56 +00:00
|
|
|
|
self.text_win.resize(self.height-3-self.core.information_win_size, self.width, 0, 0, self.core.stdscr)
|
|
|
|
|
self.info_header.resize(1, self.width, self.height-3-self.core.information_win_size, 0, self.core.stdscr)
|
|
|
|
|
self.info_win.resize(self.core.information_win_size, self.width, self.height-2-self.core.information_win_size, 0, self.core.stdscr)
|
|
|
|
|
self.tab_win.resize(1, self.width, self.height-2, 0, self.core.stdscr)
|
|
|
|
|
self.input.resize(1, self.width, self.height-1, 0, self.core.stdscr)
|
2010-09-14 02:11:07 +00:00
|
|
|
|
|
2010-09-26 18:01:38 +00:00
|
|
|
|
def refresh(self, tabs, informations, _):
|
2010-11-22 02:39:56 +00:00
|
|
|
|
if not self.visible:
|
|
|
|
|
return
|
2010-09-14 02:11:07 +00:00
|
|
|
|
self.text_win.refresh(self._room)
|
|
|
|
|
self.info_header.refresh(self._room)
|
|
|
|
|
self.info_win.refresh(informations)
|
|
|
|
|
self.tab_win.refresh(tabs, tabs[0])
|
|
|
|
|
self.input.refresh()
|
|
|
|
|
|
|
|
|
|
def get_color_state(self):
|
|
|
|
|
if self._room.color_state == theme.COLOR_TAB_NORMAL or\
|
|
|
|
|
self._room.color_state == theme.COLOR_TAB_CURRENT:
|
|
|
|
|
return self._room.color_state
|
|
|
|
|
return theme.COLOR_TAB_PRIVATE
|
|
|
|
|
|
|
|
|
|
def set_color_state(self, color):
|
|
|
|
|
self._room.color_state = color
|
|
|
|
|
|
|
|
|
|
def get_name(self):
|
|
|
|
|
return self._room.name
|
|
|
|
|
|
|
|
|
|
def on_input(self, key):
|
2010-11-22 02:00:03 +00:00
|
|
|
|
if key in self.key_func:
|
|
|
|
|
self.key_func[key]()
|
2010-11-15 13:16:30 +00:00
|
|
|
|
return False
|
2010-11-23 13:46:19 +00:00
|
|
|
|
self.input.do_command(key)
|
|
|
|
|
return False
|
2010-09-14 02:11:07 +00:00
|
|
|
|
|
2010-11-11 20:44:14 +00:00
|
|
|
|
def on_enter(self):
|
|
|
|
|
"""
|
|
|
|
|
When enter is pressed, send the message to the Muc
|
|
|
|
|
"""
|
|
|
|
|
self.core.execute(self.input.key_enter())
|
|
|
|
|
|
2010-09-14 02:11:07 +00:00
|
|
|
|
def on_lose_focus(self):
|
|
|
|
|
self._room.set_color_state(theme.COLOR_TAB_NORMAL)
|
|
|
|
|
self._room.remove_line_separator()
|
2010-09-14 22:59:40 +00:00
|
|
|
|
self._room.add_line_separator()
|
2010-09-14 02:11:07 +00:00
|
|
|
|
|
|
|
|
|
def on_gain_focus(self):
|
|
|
|
|
self._room.set_color_state(theme.COLOR_TAB_CURRENT)
|
2010-11-10 21:15:08 +00:00
|
|
|
|
curses.curs_set(1)
|
2010-09-14 02:11:07 +00:00
|
|
|
|
|
|
|
|
|
def on_scroll_up(self):
|
|
|
|
|
self._room.scroll_up(self.text_win.height-1)
|
|
|
|
|
|
|
|
|
|
def on_scroll_down(self):
|
|
|
|
|
self._room.scroll_down(self.text_win.height-1)
|
|
|
|
|
|
2010-11-19 16:27:20 +00:00
|
|
|
|
def on_info_win_size_changed(self):
|
2010-11-22 02:39:56 +00:00
|
|
|
|
self.text_win.resize(self.height-3-self.core.information_win_size, self.width, 0, 0, self.core.stdscr)
|
|
|
|
|
self.info_header.resize(1, self.width, self.height-3-self.core.information_win_size, 0, self.core.stdscr)
|
|
|
|
|
self.info_win.resize(self.core.information_win_size, self.width, self.height-2-self.core.information_win_size, 0, self.core.stdscr)
|
2010-09-14 02:11:07 +00:00
|
|
|
|
|
|
|
|
|
def get_room(self):
|
|
|
|
|
return self._room
|
2010-09-15 19:05:20 +00:00
|
|
|
|
|
2010-10-17 05:14:22 +00:00
|
|
|
|
def just_before_refresh(self):
|
|
|
|
|
return
|
|
|
|
|
|
2010-11-11 03:39:39 +00:00
|
|
|
|
def on_close(self):
|
|
|
|
|
return
|
|
|
|
|
|
2010-09-15 19:05:20 +00:00
|
|
|
|
class RosterInfoTab(Tab):
|
|
|
|
|
"""
|
2010-09-26 18:01:38 +00:00
|
|
|
|
A tab, splitted in two, containing the roster and infos
|
2010-09-15 19:05:20 +00:00
|
|
|
|
"""
|
2010-11-19 16:27:20 +00:00
|
|
|
|
def __init__(self, core):
|
|
|
|
|
Tab.__init__(self, core)
|
2010-09-15 19:05:20 +00:00
|
|
|
|
self.name = "Roster"
|
2010-11-22 02:25:15 +00:00
|
|
|
|
self.v_separator = windows.VerticalSeparator()
|
|
|
|
|
self.tab_win = windows.GlobalInfoBar()
|
|
|
|
|
self.info_win = windows.TextWin()
|
|
|
|
|
self.roster_win = windows.RosterWin()
|
|
|
|
|
self.contact_info_win = windows.ContactInfoWin()
|
|
|
|
|
self.default_help_message = windows.HelpText("Enter commands with “/”. “o”: toggle offline show")
|
2010-11-11 20:44:14 +00:00
|
|
|
|
self.input = self.default_help_message
|
2010-09-15 19:05:20 +00:00
|
|
|
|
self.set_color_state(theme.COLOR_TAB_NORMAL)
|
2010-11-22 02:25:15 +00:00
|
|
|
|
self.resize()
|
2010-09-15 19:05:20 +00:00
|
|
|
|
|
2010-11-19 16:27:20 +00:00
|
|
|
|
def resize(self):
|
|
|
|
|
Tab.resize(self)
|
2010-09-15 19:05:20 +00:00
|
|
|
|
roster_width = self.width//2
|
|
|
|
|
info_width = self.width-roster_width-1
|
2010-11-22 02:39:56 +00:00
|
|
|
|
self.v_separator.resize(self.height-2, 1, 0, roster_width, self.core.stdscr)
|
|
|
|
|
self.tab_win.resize(1, self.width, self.height-2, 0, self.core.stdscr)
|
|
|
|
|
self.info_win.resize(self.height-2, info_width, 0, roster_width+1, self.core.stdscr)
|
|
|
|
|
self.roster_win.resize(self.height-2-3, roster_width, 0, 0, self.core.stdscr)
|
|
|
|
|
self.contact_info_win.resize(3, roster_width, self.height-2-3, 0, self.core.stdscr)
|
|
|
|
|
self.input.resize(1, self.width, self.height-1, 0, self.core.stdscr)
|
2010-09-15 19:05:20 +00:00
|
|
|
|
|
2010-09-26 18:01:38 +00:00
|
|
|
|
def refresh(self, tabs, informations, roster):
|
2010-11-22 02:39:56 +00:00
|
|
|
|
if not self.visible:
|
|
|
|
|
return
|
2010-10-17 05:14:22 +00:00
|
|
|
|
self.v_separator.refresh()
|
2010-09-26 18:01:38 +00:00
|
|
|
|
self.roster_win.refresh(roster)
|
2010-10-04 00:27:40 +00:00
|
|
|
|
self.contact_info_win.refresh(self.roster_win.get_selected_row())
|
2010-09-15 19:05:20 +00:00
|
|
|
|
self.info_win.refresh(informations)
|
|
|
|
|
self.tab_win.refresh(tabs, tabs[0])
|
|
|
|
|
self.input.refresh()
|
|
|
|
|
|
|
|
|
|
def get_name(self):
|
|
|
|
|
return self.name
|
|
|
|
|
|
|
|
|
|
def get_color_state(self):
|
|
|
|
|
return self._color_state
|
|
|
|
|
|
|
|
|
|
def set_color_state(self, color):
|
|
|
|
|
self._color_state = color
|
|
|
|
|
|
|
|
|
|
def on_input(self, key):
|
2010-11-11 20:44:14 +00:00
|
|
|
|
key_commands = {
|
|
|
|
|
"^J": self.on_enter,
|
|
|
|
|
"^M": self.on_enter,
|
|
|
|
|
"\n": self.on_enter,
|
|
|
|
|
' ': self.on_space,
|
|
|
|
|
"/": self.on_slash,
|
|
|
|
|
"KEY_UP": self.move_cursor_up,
|
|
|
|
|
"KEY_DOWN": self.move_cursor_down,
|
|
|
|
|
"o": self.toggle_offline_show,
|
|
|
|
|
"^F": self.start_search,
|
|
|
|
|
}
|
|
|
|
|
res = self.input.do_command(key)
|
|
|
|
|
if res:
|
2010-11-15 13:16:30 +00:00
|
|
|
|
return False
|
2010-11-11 20:44:14 +00:00
|
|
|
|
if key in key_commands:
|
|
|
|
|
return key_commands[key]()
|
2010-10-27 22:49:52 +00:00
|
|
|
|
|
|
|
|
|
def toggle_offline_show(self):
|
|
|
|
|
"""
|
|
|
|
|
Show or hide offline contacts
|
|
|
|
|
"""
|
|
|
|
|
option = 'roster_show_offline'
|
|
|
|
|
if config.get(option, 'false') == 'false':
|
|
|
|
|
config.set_and_save(option, 'true')
|
|
|
|
|
else:
|
|
|
|
|
config.set_and_save(option, 'false')
|
|
|
|
|
return True
|
2010-10-31 18:57:48 +00:00
|
|
|
|
|
2010-10-27 22:49:52 +00:00
|
|
|
|
def on_slash(self):
|
|
|
|
|
"""
|
|
|
|
|
'/' is pressed, we enter "input mode"
|
|
|
|
|
"""
|
|
|
|
|
curses.curs_set(1)
|
2010-11-22 02:48:24 +00:00
|
|
|
|
self.input = windows.CommandInput("", self.reset_help_message, self.execute_slash_command)
|
|
|
|
|
self.input.resize(1, self.width, self.height-1, 0, self.core.stdscr)
|
2010-11-11 20:44:14 +00:00
|
|
|
|
self.input.do_command("/") # we add the slash
|
|
|
|
|
|
|
|
|
|
def reset_help_message(self, _=None):
|
|
|
|
|
curses.curs_set(0)
|
|
|
|
|
self.input = self.default_help_message
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
def execute_slash_command(self, txt):
|
|
|
|
|
if txt.startswith('/'):
|
|
|
|
|
self.core.execute(txt)
|
|
|
|
|
return self.reset_help_message()
|
2010-09-15 19:05:20 +00:00
|
|
|
|
|
|
|
|
|
def on_lose_focus(self):
|
|
|
|
|
self._color_state = theme.COLOR_TAB_NORMAL
|
|
|
|
|
|
|
|
|
|
def on_gain_focus(self):
|
|
|
|
|
self._color_state = theme.COLOR_TAB_CURRENT
|
2010-10-17 05:14:22 +00:00
|
|
|
|
curses.curs_set(0)
|
2010-09-15 19:05:20 +00:00
|
|
|
|
|
|
|
|
|
def add_message(self):
|
|
|
|
|
return False
|
|
|
|
|
|
2010-10-27 22:49:52 +00:00
|
|
|
|
def move_cursor_down(self):
|
2010-09-26 18:01:38 +00:00
|
|
|
|
self.roster_win.move_cursor_down()
|
2010-10-27 22:49:52 +00:00
|
|
|
|
return True
|
2010-09-15 19:05:20 +00:00
|
|
|
|
|
2010-10-27 22:49:52 +00:00
|
|
|
|
def move_cursor_up(self):
|
2010-09-26 18:01:38 +00:00
|
|
|
|
self.roster_win.move_cursor_up()
|
2010-10-27 22:49:52 +00:00
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
def on_scroll_down(self):
|
|
|
|
|
# Scroll info win
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
def on_scroll_up(self):
|
|
|
|
|
# Scroll info down
|
|
|
|
|
pass
|
2010-09-15 19:05:20 +00:00
|
|
|
|
|
2010-11-19 16:27:20 +00:00
|
|
|
|
def on_info_win_size_changed(self):
|
2010-09-26 18:01:38 +00:00
|
|
|
|
pass
|
|
|
|
|
|
2010-10-17 17:27:07 +00:00
|
|
|
|
def on_space(self):
|
|
|
|
|
selected_row = self.roster_win.get_selected_row()
|
|
|
|
|
if isinstance(selected_row, RosterGroup) or\
|
|
|
|
|
isinstance(selected_row, Contact):
|
|
|
|
|
selected_row.toggle_folded()
|
|
|
|
|
return True
|
2010-10-31 18:57:48 +00:00
|
|
|
|
|
2010-09-26 18:01:38 +00:00
|
|
|
|
def on_enter(self):
|
2010-09-27 01:40:34 +00:00
|
|
|
|
selected_row = self.roster_win.get_selected_row()
|
2010-11-11 20:44:14 +00:00
|
|
|
|
self.core.on_roster_enter_key(selected_row)
|
2010-09-27 01:40:34 +00:00
|
|
|
|
return selected_row
|
2010-09-26 18:01:38 +00:00
|
|
|
|
|
2010-10-31 18:57:48 +00:00
|
|
|
|
def start_search(self):
|
|
|
|
|
"""
|
|
|
|
|
Start the search. The input should appear with a short instruction
|
|
|
|
|
in it.
|
|
|
|
|
"""
|
|
|
|
|
curses.curs_set(1)
|
2010-11-22 02:39:56 +00:00
|
|
|
|
self.input = windows.CommandInput(1, self.width, self.height-1, 0, self.default_help_message, "[Search]", self.on_search_terminate, self.on_search_terminate, self.set_roster_filter)
|
2010-10-31 18:57:48 +00:00
|
|
|
|
return True
|
|
|
|
|
|
2010-11-11 20:44:14 +00:00
|
|
|
|
def set_roster_filter(self, txt):
|
|
|
|
|
roster._contact_filter = (jid_and_name_match, txt)
|
|
|
|
|
|
2010-10-31 18:57:48 +00:00
|
|
|
|
def on_search_terminate(self, txt):
|
|
|
|
|
curses.curs_set(0)
|
|
|
|
|
roster._contact_filter = None
|
2010-11-11 20:44:14 +00:00
|
|
|
|
self.reset_help_message()
|
|
|
|
|
return False
|
2010-10-31 18:57:48 +00:00
|
|
|
|
|
2010-10-17 05:14:22 +00:00
|
|
|
|
def just_before_refresh(self):
|
|
|
|
|
return
|
|
|
|
|
|
2010-11-11 03:39:39 +00:00
|
|
|
|
def on_close(self):
|
|
|
|
|
return
|
|
|
|
|
|
2010-11-22 02:00:03 +00:00
|
|
|
|
class ConversationTab(ChatTab):
|
2010-09-26 18:01:38 +00:00
|
|
|
|
"""
|
|
|
|
|
The tab containg a normal conversation (someone from our roster)
|
|
|
|
|
"""
|
2010-11-19 16:27:20 +00:00
|
|
|
|
def __init__(self, core, text_buffer, jid):
|
2010-11-22 02:00:03 +00:00
|
|
|
|
ChatTab.__init__(self, core, text_buffer)
|
2010-11-10 21:15:08 +00:00
|
|
|
|
self.color_state = theme.COLOR_TAB_NORMAL
|
|
|
|
|
self._name = jid # a conversation tab is linked to one specific full jid OR bare jid
|
2010-11-22 02:25:15 +00:00
|
|
|
|
self.text_win = windows.TextWin()
|
|
|
|
|
self.upper_bar = windows.ConversationStatusMessageWin()
|
|
|
|
|
self.info_header = windows.ConversationInfoWin()
|
|
|
|
|
self.info_win = windows.TextWin()
|
|
|
|
|
self.tab_win = windows.GlobalInfoBar()
|
|
|
|
|
self.input = windows.MessageInput()
|
|
|
|
|
self.resize()
|
2010-11-19 16:27:20 +00:00
|
|
|
|
|
|
|
|
|
def resize(self):
|
|
|
|
|
Tab.resize(self)
|
2010-11-22 02:39:56 +00:00
|
|
|
|
self.text_win.resize(self.height-3-self.core.information_win_size, self.width, 0, 0, self.core.stdscr)
|
|
|
|
|
self.upper_bar.resize(1, self.width, 0, 0, self.core.stdscr)
|
|
|
|
|
self.info_header.resize(1, self.width, self.height-3-self.core.information_win_size, 0, self.core.stdscr)
|
|
|
|
|
self.info_win.resize(self.core.information_win_size, self.width, self.height-2-self.core.information_win_size, 0, self.core.stdscr)
|
|
|
|
|
self.tab_win.resize(1, self.width, self.height-2, 0, self.core.stdscr)
|
|
|
|
|
self.input.resize(1, self.width, self.height-1, 0, self.core.stdscr)
|
2010-09-26 18:01:38 +00:00
|
|
|
|
|
2010-09-27 01:40:34 +00:00
|
|
|
|
def refresh(self, tabs, informations, roster):
|
2010-11-22 02:39:56 +00:00
|
|
|
|
if not self.visible:
|
|
|
|
|
return
|
2010-11-22 02:00:03 +00:00
|
|
|
|
self.text_win.refresh(self._room)
|
2010-11-10 21:15:08 +00:00
|
|
|
|
self.upper_bar.refresh(self.get_name(), roster.get_contact_by_jid(self.get_name()))
|
2010-11-22 02:00:03 +00:00
|
|
|
|
self.info_header.refresh(self.get_name(), roster.get_contact_by_jid(self.get_name()), self._room)
|
2010-09-26 18:01:38 +00:00
|
|
|
|
self.info_win.refresh(informations)
|
|
|
|
|
self.tab_win.refresh(tabs, tabs[0])
|
|
|
|
|
self.input.refresh()
|
|
|
|
|
|
|
|
|
|
def get_color_state(self):
|
2010-11-10 21:15:08 +00:00
|
|
|
|
if self.color_state == theme.COLOR_TAB_NORMAL or\
|
|
|
|
|
self.color_state == theme.COLOR_TAB_CURRENT:
|
|
|
|
|
return self.color_state
|
2010-09-26 18:01:38 +00:00
|
|
|
|
return theme.COLOR_TAB_PRIVATE
|
|
|
|
|
|
|
|
|
|
def set_color_state(self, color):
|
2010-11-10 21:15:08 +00:00
|
|
|
|
self.color_state = color
|
2010-09-26 18:01:38 +00:00
|
|
|
|
|
|
|
|
|
def get_name(self):
|
2010-11-10 21:15:08 +00:00
|
|
|
|
return self._name
|
2010-09-26 18:01:38 +00:00
|
|
|
|
|
|
|
|
|
def on_input(self, key):
|
2010-11-22 02:00:03 +00:00
|
|
|
|
if key in self.key_func:
|
|
|
|
|
self.key_func[key]()
|
2010-11-15 13:16:30 +00:00
|
|
|
|
return False
|
2010-11-19 16:27:20 +00:00
|
|
|
|
self.input.do_command(key)
|
2010-11-23 13:46:19 +00:00
|
|
|
|
return False
|
2010-09-26 18:01:38 +00:00
|
|
|
|
|
2010-11-11 20:44:14 +00:00
|
|
|
|
def on_enter(self):
|
|
|
|
|
"""
|
|
|
|
|
When enter is pressed, send the message to the Muc
|
|
|
|
|
"""
|
|
|
|
|
self.core.execute(self.input.key_enter())
|
|
|
|
|
|
2010-09-26 18:01:38 +00:00
|
|
|
|
def on_lose_focus(self):
|
2010-11-10 21:15:08 +00:00
|
|
|
|
self.set_color_state(theme.COLOR_TAB_NORMAL)
|
2010-11-22 02:00:03 +00:00
|
|
|
|
self._room.remove_line_separator()
|
|
|
|
|
self._room.add_line_separator()
|
2010-09-26 18:01:38 +00:00
|
|
|
|
|
|
|
|
|
def on_gain_focus(self):
|
2010-11-10 21:15:08 +00:00
|
|
|
|
self.set_color_state(theme.COLOR_TAB_CURRENT)
|
|
|
|
|
curses.curs_set(1)
|
2010-09-26 18:01:38 +00:00
|
|
|
|
|
|
|
|
|
def on_scroll_up(self):
|
2010-11-22 02:00:03 +00:00
|
|
|
|
self._room.scroll_up(self.text_win.height-1)
|
2010-09-26 18:01:38 +00:00
|
|
|
|
|
|
|
|
|
def on_scroll_down(self):
|
2010-11-22 02:00:03 +00:00
|
|
|
|
self._room.scroll_down(self.text_win.height-1)
|
2010-09-26 18:01:38 +00:00
|
|
|
|
|
2010-11-19 16:27:20 +00:00
|
|
|
|
def on_info_win_size_changed(self):
|
2010-11-22 02:39:56 +00:00
|
|
|
|
self.text_win.resize(self.height-3-self.core.information_win_size, self.width, 0, 0, self.core.stdscr)
|
|
|
|
|
self.info_header.resize(1, self.width, self.height-3-self.core.information_win_size, 0, self.core.stdscr)
|
|
|
|
|
self.info_win.resize(self.core.information_win_size, self.width, self.height-2-self.core.information_win_size, 0, self.core.stdscr)
|
2010-09-26 18:01:38 +00:00
|
|
|
|
|
|
|
|
|
def get_room(self):
|
2010-11-22 02:00:03 +00:00
|
|
|
|
return self._room
|
2010-10-17 05:14:22 +00:00
|
|
|
|
|
|
|
|
|
def just_before_refresh(self):
|
|
|
|
|
return
|
2010-10-31 18:57:48 +00:00
|
|
|
|
|
2010-11-11 03:39:39 +00:00
|
|
|
|
def on_close(self):
|
|
|
|
|
return
|
|
|
|
|
|
2010-11-11 05:24:43 +00:00
|
|
|
|
def diffmatch(search, string):
|
|
|
|
|
"""
|
|
|
|
|
Use difflib and a loop to check if search_pattern can
|
|
|
|
|
be 'almost' found INSIDE a string.
|
|
|
|
|
'almost' being defined by difflib
|
|
|
|
|
"""
|
|
|
|
|
l = len(search)
|
|
|
|
|
ratio = 0.7
|
|
|
|
|
for i in range(len(string) - l + 1):
|
|
|
|
|
if difflib.SequenceMatcher(None, search, string[i:i+l]).ratio() >= ratio:
|
|
|
|
|
return True
|
|
|
|
|
return False
|
|
|
|
|
|
2010-10-31 18:57:48 +00:00
|
|
|
|
def jid_and_name_match(contact, txt):
|
|
|
|
|
"""
|
|
|
|
|
A function used to know if a contact in the roster should
|
|
|
|
|
be shown in the roster
|
|
|
|
|
"""
|
2010-11-11 05:24:43 +00:00
|
|
|
|
ratio = 0.7
|
|
|
|
|
if not txt:
|
|
|
|
|
return True # Everything matches when search is empty
|
|
|
|
|
user = JID(contact.get_bare_jid()).user
|
|
|
|
|
if diffmatch(txt, user):
|
|
|
|
|
return True
|
|
|
|
|
if contact.get_name() and diffmatch(txt, contact.get_name()):
|
2010-10-31 18:57:48 +00:00
|
|
|
|
return True
|
|
|
|
|
return False
|