Inputs are more modulable (they also have a common history and clipboard)....

Inputs are more modulable (they also have a common history and clipboard). Search is now fully functional, and some other stuff
parent 8bdab491
...@@ -155,7 +155,6 @@ class Core(object): ...@@ -155,7 +155,6 @@ class Core(object):
self.xmpp.add_event_handler("roster_update", self.on_roster_update) self.xmpp.add_event_handler("roster_update", self.on_roster_update)
self.xmpp.add_event_handler("changed_status", self.on_presence) self.xmpp.add_event_handler("changed_status", self.on_presence)
# self.__debug_fill_roster()
def grow_information_win(self): def grow_information_win(self):
""" """
...@@ -486,13 +485,10 @@ class Core(object): ...@@ -486,13 +485,10 @@ class Core(object):
""" """
""" """
jid = presence['from'] jid = presence['from']
log.debug('Presence Received: %s\n' % presence)
contact = roster.get_contact_by_jid(jid.bare) contact = roster.get_contact_by_jid(jid.bare)
log.debug('Contact: %s\n' % contact)
if not contact: if not contact:
return return
resource = contact.get_resource_by_fulljid(jid.full) resource = contact.get_resource_by_fulljid(jid.full)
log.debug('Resource: %s\n' % resource)
if not resource: if not resource:
return return
status = presence['type'] status = presence['type']
...@@ -504,34 +500,6 @@ class Core(object): ...@@ -504,34 +500,6 @@ class Core(object):
if isinstance(self.current_tab(), RosterInfoTab): if isinstance(self.current_tab(), RosterInfoTab):
self.refresh_window() self.refresh_window()
def __debug_fill_roster(self):
for i in range(10):
jid = 'contact%s@fion%s.org'%(i,i)
contact = Contact(jid)
contact.set_ask('wat')
contact.set_subscription('both')
roster.add_contact(contact, jid)
contact.set_name('%s %s fion'%(i,i))
roster.edit_groups_of_contact(contact, ['hello'])
for i in range(10):
jid = 'test%s@bernard%s.org'%(i,i)
contact = Contact(jid)
contact.set_ask('wat')
contact.set_subscription('both')
roster.add_contact(contact, jid)
contact.set_name('%s test'%(i))
roster.edit_groups_of_contact(contact, ['hello'])
for i in range(10):
jid = 'pouet@top%s.org'%(i)
contact = Contact(jid)
contact.set_ask('wat')
contact.set_subscription('both')
roster.add_contact(contact, jid)
contact.set_name('%s oula'%(i))
roster.edit_groups_of_contact(contact, ['hello'])
if isinstance(self.current_tab(), RosterInfoTab):
self.refresh_window()
def on_roster_update(self, iq): def on_roster_update(self, iq):
""" """
A subscription changed, or we received a roster item A subscription changed, or we received a roster item
...@@ -1374,20 +1342,13 @@ class Core(object): ...@@ -1374,20 +1342,13 @@ class Core(object):
if not key: if not key:
return return
res = self.current_tab().on_input(key) res = self.current_tab().on_input(key)
if not res: self.refresh_window()
return
if key in ('^J', '\n') and isinstance(res, str):
self.execute(res)
else :
# we did "enter" with an empty input in the roster
self.on_roster_enter_key(res)
def on_roster_enter_key(self, roster_row): def on_roster_enter_key(self, roster_row):
""" """
when enter is pressed on the roster window when enter is pressed on the roster window
""" """
if isinstance(roster_row, Contact): if isinstance(roster_row, Contact):
# roster_row.toggle_folded()
if not self.get_conversation_by_jid(roster_row.get_bare_jid()): if not self.get_conversation_by_jid(roster_row.get_bare_jid()):
self.open_conversation_window(roster_row.get_bare_jid()) self.open_conversation_window(roster_row.get_bare_jid())
else: else:
...@@ -1427,7 +1388,6 @@ class Core(object): ...@@ -1427,7 +1388,6 @@ class Core(object):
muc.send_private_message(self.xmpp, self.current_tab().get_name(), line) muc.send_private_message(self.xmpp, self.current_tab().get_name(), line)
if isinstance(self.current_tab(), PrivateTab) or\ if isinstance(self.current_tab(), PrivateTab) or\
isinstance(self.current_tab(), ConversationTab): isinstance(self.current_tab(), ConversationTab):
log.debug('ALLO ICI\n\n')
self.add_message_to_text_buffer(self.current_tab().get_room(), line, None, self.own_nick) self.add_message_to_text_buffer(self.current_tab().get_room(), line, None, self.own_nick)
elif isinstance(self.current_tab(), MucTab): elif isinstance(self.current_tab(), MucTab):
muc.send_groupchat_message(self.xmpp, self.current_tab().get_name(), line) muc.send_groupchat_message(self.xmpp, self.current_tab().get_name(), line)
......
...@@ -216,7 +216,7 @@ class MucTab(Tab): ...@@ -216,7 +216,7 @@ class MucTab(Tab):
self.info_header = window.MucInfoWin(1, (self.width//10)*9, self.height-3-self.core.information_win_size, 0, stdscr, self.visible) self.info_header = window.MucInfoWin(1, (self.width//10)*9, self.height-3-self.core.information_win_size, 0, stdscr, self.visible)
self.info_win = window.TextWin(self.core.information_win_size, (self.width//10)*9, self.height-2-self.core.information_win_size, 0, stdscr, self.visible) self.info_win = window.TextWin(self.core.information_win_size, (self.width//10)*9, self.height-2-self.core.information_win_size, 0, stdscr, self.visible)
self.tab_win = window.GlobalInfoBar(1, self.width, self.height-2, 0, stdscr, self.visible) self.tab_win = window.GlobalInfoBar(1, self.width, self.height-2, 0, stdscr, self.visible)
self.input = window.Input(1, self.width, self.height-1, 0, stdscr, self.visible) self.input = window.MessageInput(1, self.width, self.height-1, 0, stdscr, self.visible)
def resize(self, stdscr): def resize(self, stdscr):
""" """
...@@ -244,14 +244,17 @@ class MucTab(Tab): ...@@ -244,14 +244,17 @@ class MucTab(Tab):
self.input.refresh() self.input.refresh()
def on_input(self, key): def on_input(self, key):
self.key_func = { key_func = {
"\t": self.completion, "\t": self.completion,
"^I": self.completion, "^I": self.completion,
"M-i": self.completion, "M-i": self.completion,
"KEY_BTAB": self.last_words_completion, "KEY_BTAB": self.last_words_completion,
"^J": self.on_enter,
"^M": self.on_enter,
"\n": self.on_enter
} }
if key in self.key_func: if key in key_func:
return self.key_func[key]() return key_func[key]()
return self.input.do_command(key) return self.input.do_command(key)
def completion(self): def completion(self):
...@@ -271,7 +274,6 @@ class MucTab(Tab): ...@@ -271,7 +274,6 @@ class MucTab(Tab):
for msg in self._room.messages[:-40:-1]: for msg in self._room.messages[:-40:-1]:
if not msg: if not msg:
continue continue
log.debug('line: %s\n'%msg)
for char in char_we_dont_want: for char in char_we_dont_want:
msg.txt = msg.txt.replace(char, ' ') msg.txt = msg.txt.replace(char, ' ')
for word in msg.txt.split(): for word in msg.txt.split():
...@@ -279,6 +281,12 @@ class MucTab(Tab): ...@@ -279,6 +281,12 @@ class MucTab(Tab):
words.append(word) words.append(word)
self.input.auto_completion(words, False) self.input.auto_completion(words, False)
def on_enter(self):
"""
When enter is pressed, send the message to the Muc
"""
self.core.execute(self.input.key_enter())
def get_color_state(self): def get_color_state(self):
return self._room.color_state return self._room.color_state
...@@ -313,7 +321,7 @@ class MucTab(Tab): ...@@ -313,7 +321,7 @@ class MucTab(Tab):
self.info_win.resize(self.core.information_win_size, (self.width//10)*9, self.height-2-self.core.information_win_size, 0, stdscr, self.visible) self.info_win.resize(self.core.information_win_size, (self.width//10)*9, self.height-2-self.core.information_win_size, 0, stdscr, self.visible)
def just_before_refresh(self): def just_before_refresh(self):
self.input.move_cursor_to_pos() return
def on_close(self): def on_close(self):
return return
...@@ -329,7 +337,7 @@ class PrivateTab(Tab): ...@@ -329,7 +337,7 @@ class PrivateTab(Tab):
self.info_header = window.PrivateInfoWin(1, self.width, self.height-3-self.core.information_win_size, 0, stdscr, self.visible) self.info_header = window.PrivateInfoWin(1, self.width, self.height-3-self.core.information_win_size, 0, stdscr, self.visible)
self.info_win = window.TextWin(self.core.information_win_size, self.width, self.height-2-self.core.information_win_size, 0, stdscr, self.visible) self.info_win = window.TextWin(self.core.information_win_size, self.width, self.height-2-self.core.information_win_size, 0, stdscr, self.visible)
self.tab_win = window.GlobalInfoBar(1, self.width, self.height-2, 0, stdscr, self.visible) self.tab_win = window.GlobalInfoBar(1, self.width, self.height-2, 0, stdscr, self.visible)
self.input = window.Input(1, self.width, self.height-1, 0, stdscr, self.visible) self.input = window.MessageInput(1, self.width, self.height-1, 0, stdscr, self.visible)
def resize(self, stdscr): def resize(self, stdscr):
Tab.resize(self, stdscr) Tab.resize(self, stdscr)
...@@ -359,8 +367,21 @@ class PrivateTab(Tab): ...@@ -359,8 +367,21 @@ class PrivateTab(Tab):
return self._room.name return self._room.name
def on_input(self, key): def on_input(self, key):
key_func = {
"^J": self.on_enter,
"^M": self.on_enter,
"\n": self.on_enter
}
if key in key_func:
return key_func[key]()
return self.input.do_command(key) return self.input.do_command(key)
def on_enter(self):
"""
When enter is pressed, send the message to the Muc
"""
self.core.execute(self.input.key_enter())
def on_lose_focus(self): def on_lose_focus(self):
self._room.set_color_state(theme.COLOR_TAB_NORMAL) self._room.set_color_state(theme.COLOR_TAB_NORMAL)
self._room.remove_line_separator() self._room.remove_line_separator()
...@@ -395,17 +416,6 @@ class RosterInfoTab(Tab): ...@@ -395,17 +416,6 @@ class RosterInfoTab(Tab):
A tab, splitted in two, containing the roster and infos A tab, splitted in two, containing the roster and infos
""" """
def __init__(self, stdscr, core): def __init__(self, stdscr, core):
self.single_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,
}
Tab.__init__(self, stdscr, core) Tab.__init__(self, stdscr, core)
self.name = "Roster" self.name = "Roster"
roster_width = self.width//2 roster_width = self.width//2
...@@ -415,7 +425,8 @@ class RosterInfoTab(Tab): ...@@ -415,7 +425,8 @@ class RosterInfoTab(Tab):
self.info_win = window.TextWin(self.height-2, info_width, 0, roster_width+1, stdscr, self.visible) self.info_win = window.TextWin(self.height-2, info_width, 0, roster_width+1, stdscr, self.visible)
self.roster_win = window.RosterWin(self.height-2-3, roster_width, 0, 0, stdscr, self.visible) self.roster_win = window.RosterWin(self.height-2-3, roster_width, 0, 0, stdscr, self.visible)
self.contact_info_win = window.ContactInfoWin(3, roster_width, self.height-2-3, 0, stdscr, self.visible) self.contact_info_win = window.ContactInfoWin(3, roster_width, self.height-2-3, 0, stdscr, self.visible)
self.input = window.Input(1, self.width, self.height-1, 0, stdscr, self.visible, False, "Enter commands with “/”. “o”: toggle offline show") self.default_help_message = window.HelpText(1, self.width, self.height-1, 0, stdscr, self.visible, "Enter commands with “/”. “o”: toggle offline show")
self.input = self.default_help_message
self.set_color_state(theme.COLOR_TAB_NORMAL) self.set_color_state(theme.COLOR_TAB_NORMAL)
def resize(self, stdscr): def resize(self, stdscr):
...@@ -447,19 +458,22 @@ class RosterInfoTab(Tab): ...@@ -447,19 +458,22 @@ class RosterInfoTab(Tab):
self._color_state = color self._color_state = color
def on_input(self, key): def on_input(self, key):
if self.input.input_mode: key_commands = {
ret = self.input.do_command(key) "^J": self.on_enter,
roster._contact_filter = (jid_and_name_match, self.input.text) "^M": self.on_enter,
# if the input is empty, go back to command mode "\n": self.on_enter,
if self.input.is_empty() and not self.input._instructions: ' ': self.on_space,
self.input.input_mode = False "/": self.on_slash,
curses.curs_set(0) "KEY_UP": self.move_cursor_up,
self.input.rewrite_text() "KEY_DOWN": self.move_cursor_down,
if self.input._instructions: "o": self.toggle_offline_show,
return True "^F": self.start_search,
return ret }
if key in self.single_key_commands: res = self.input.do_command(key)
return self.single_key_commands[key]() if res:
return res
if key in key_commands:
return key_commands[key]()
def toggle_offline_show(self): def toggle_offline_show(self):
""" """
...@@ -476,9 +490,19 @@ class RosterInfoTab(Tab): ...@@ -476,9 +490,19 @@ class RosterInfoTab(Tab):
""" """
'/' is pressed, we enter "input mode" '/' is pressed, we enter "input mode"
""" """
self.input.input_mode = True
curses.curs_set(1) curses.curs_set(1)
self.on_input("/") # we add the slash self.input = window.CommandInput(1, self.width, self.height-1, 0, self.default_help_message, self.visible, "", self.reset_help_message, self.execute_slash_command)
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()
def on_lose_focus(self): def on_lose_focus(self):
self._color_state = theme.COLOR_TAB_NORMAL self._color_state = theme.COLOR_TAB_NORMAL
...@@ -518,6 +542,7 @@ class RosterInfoTab(Tab): ...@@ -518,6 +542,7 @@ class RosterInfoTab(Tab):
def on_enter(self): def on_enter(self):
selected_row = self.roster_win.get_selected_row() selected_row = self.roster_win.get_selected_row()
self.core.on_roster_enter_key(selected_row)
return selected_row return selected_row
def start_search(self): def start_search(self):
...@@ -526,15 +551,17 @@ class RosterInfoTab(Tab): ...@@ -526,15 +551,17 @@ class RosterInfoTab(Tab):
in it. in it.
""" """
curses.curs_set(1) curses.curs_set(1)
roster._contact_filter = (jid_and_name_match, self.input.text) self.input = window.CommandInput(1, self.width, self.height-1, 0, self.default_help_message, self.visible, "[Search]", self.on_search_terminate, self.on_search_terminate, self.set_roster_filter)
self.input.input_mode = True
self.input.start_command(self.on_search_terminate, self.on_search_terminate, '[search]')
return True return True
def set_roster_filter(self, txt):
roster._contact_filter = (jid_and_name_match, txt)
def on_search_terminate(self, txt): def on_search_terminate(self, txt):
curses.curs_set(0) curses.curs_set(0)
roster._contact_filter = None roster._contact_filter = None
return True self.reset_help_message()
return False
def just_before_refresh(self): def just_before_refresh(self):
return return
...@@ -556,7 +583,7 @@ class ConversationTab(Tab): ...@@ -556,7 +583,7 @@ class ConversationTab(Tab):
self.info_header = window.ConversationInfoWin(1, self.width, self.height-3-self.core.information_win_size, 0, stdscr, self.visible) self.info_header = window.ConversationInfoWin(1, self.width, self.height-3-self.core.information_win_size, 0, stdscr, self.visible)
self.info_win = window.TextWin(self.core.information_win_size, self.width, self.height-2-self.core.information_win_size, 0, stdscr, self.visible) self.info_win = window.TextWin(self.core.information_win_size, self.width, self.height-2-self.core.information_win_size, 0, stdscr, self.visible)
self.tab_win = window.GlobalInfoBar(1, self.width, self.height-2, 0, stdscr, self.visible) self.tab_win = window.GlobalInfoBar(1, self.width, self.height-2, 0, stdscr, self.visible)
self.input = window.Input(1, self.width, self.height-1, 0, stdscr, self.visible) self.input = window.MessageInput(1, self.width, self.height-1, 0, stdscr, self.visible)
def resize(self, stdscr): def resize(self, stdscr):
Tab.resize(self, stdscr) Tab.resize(self, stdscr)
...@@ -588,8 +615,22 @@ class ConversationTab(Tab): ...@@ -588,8 +615,22 @@ class ConversationTab(Tab):
return self._name return self._name
def on_input(self, key): def on_input(self, key):
key_func = {
"^J": self.on_enter,
"^M": self.on_enter,
"\n": self.on_enter
}
if key in key_func:
return key_func[key]()
return self.input.do_command(key) return self.input.do_command(key)
def on_enter(self):
"""
When enter is pressed, send the message to the Muc
"""
self.core.execute(self.input.key_enter())
def on_lose_focus(self): def on_lose_focus(self):
self.set_color_state(theme.COLOR_TAB_NORMAL) self.set_color_state(theme.COLOR_TAB_NORMAL)
self._text_buffer.remove_line_separator() self._text_buffer.remove_line_separator()
......
...@@ -598,25 +598,54 @@ class TextWin(Win): ...@@ -598,25 +598,54 @@ class TextWin(Win):
self.visible = visible self.visible = visible
self._resize(height, width, y, x, stdscr, 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): class Input(Win):
""" """
The line where text is entered The simplest Input possible, provides just a way to edit a single line
It can be in input mode or in commmand mode. of text. It also has a clipboard, common to all Inputs.
Command mode means that single_key_commands can be entered, handled Doesn't have any history.
by the Tab object, while this input just displays an help text. 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.
""" """
def __init__(self, height, width, y, x, stdscr, visible, input_mode=True, help_text=''): 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 = { self.key_func = {
"KEY_LEFT": self.key_left, "KEY_LEFT": self.key_left,
"M-D": self.key_left, "M-D": self.key_left,
"KEY_RIGHT": self.key_right, "KEY_RIGHT": self.key_right,
"M-C": self.key_right, "M-C": self.key_right,
"KEY_UP": self.key_up,
"M-A": self.key_up,
"KEY_END": self.key_end, "KEY_END": self.key_end,
"KEY_HOME": self.key_home, "KEY_HOME": self.key_home,
"KEY_DOWN": self.key_down,
"M-B": self.key_down,
"KEY_DC": self.key_dc, "KEY_DC": self.key_dc,
'^D': self.key_dc, '^D': self.key_dc,
'M-b': self.jump_word_left, 'M-b': self.jump_word_left,
...@@ -629,69 +658,13 @@ class Input(Win): ...@@ -629,69 +658,13 @@ class Input(Win):
'M-f': self.jump_word_right, 'M-f': self.jump_word_right,
"KEY_BACKSPACE": self.key_backspace, "KEY_BACKSPACE": self.key_backspace,
'^?': self.key_backspace, '^?': self.key_backspace,
'^J': self.on_enter,
'\n': self.on_enter,
} }
Win.__init__(self, height, width, y, x, stdscr) Win.__init__(self, height, width, y, x, stdscr)
self.input_mode = input_mode
self.help_text = help_text # the text displayed in command_mode
self.visible = visible self.visible = visible
self.history = []
self.text = '' self.text = ''
self.clipboard = None
self.pos = 0 # cursor position self.pos = 0 # cursor position
self.line_pos = 0 # position (in self.text) of self.line_pos = 0 # position (in self.text) of
# the first char to display on the screen
self.histo_pos = 0
self.hit_list = [] # current possible completion (normal)
self.last_completion = None # Contains the last nickname completed,
# if last key was a tab
# These are used when the user is entering a comand
self._on_cancel = None
self._on_terminate = None
self._instructions = "" # a string displayed before the input, read-only
def on_enter(self):
"""
Called when Enter is pressed
"""
if not self._instructions:
return self.get_text()
self.on_terminate()
return True
def start_command(self, on_cancel, on_terminate, instructions):
"""
Start a command, with an instruction, and two callbacks.
on_terminate is called when the command is successfull
on_cancel is called when the command is canceled
"""
assert isinstance(instructions, str)
self._on_cancel = on_cancel
self._on_terminate = on_terminate
self._instructions = instructions
def cancel_command(self):
"""
Call it to cancel the current command
"""
self._on_cancel()
self._on_cancel = None
self._on_terminate = None
self._instructions = ''
return self.get_text()
def on_terminate(self):
"""
Call it to terminate the command. Returns the content of the input
"""
txt = self.get_text()
self._on_terminate(txt)
self._on_terminate = None
self._on_cancel = None
self._instructions = ''
return txt
def is_empty(self): def is_empty(self):
return len(self.text) == 0 return len(self.text) == 0
...@@ -716,6 +689,7 @@ class Input(Win): ...@@ -716,6 +689,7 @@ class Input(Win):
diff = self.pos+self.line_pos-previous_space diff = self.pos+self.line_pos-previous_space
for i in range(diff): for i in range(diff):
self.key_left() self.key_left()
return True
def jump_word_right(self): def jump_word_right(self):
""" """
...@@ -729,6 +703,7 @@ class Input(Win): ...@@ -729,6 +703,7 @@ class Input(Win):
diff = next_space - (self.pos+self.line_pos) diff = next_space - (self.pos+self.line_pos)
for i in range(diff): for i in range(diff):
self.key_right() self.key_right()
return True
def delete_word(self): def delete_word(self):
""" """
...@@ -743,6 +718,7 @@ class Input(Win): ...@@ -743,6 +718,7 @@ class Input(Win):
for i in range(diff): for i in range(diff):
self.key_backspace(False) self.key_backspace(False)
self.rewrite_text() self.rewrite_text()
return True
def delete_end_of_line(self): def delete_end_of_line(self):
""" """
...@@ -750,9 +726,10 @@ class Input(Win): ...@@ -750,9 +726,10 @@ class Input(Win):
""" """
if len(self.text) == self.pos+self.line_pos: if len(self.text) == self.pos+self.line_pos:
return # nothing to cut return # nothing to cut
self.clipboard = self.text[self.pos+self.line_pos:] Input.clipboard = self.text[self.pos+self.line_pos:]