Basic search in the roster (based on contact JIDs)

parent 2863eebd
...@@ -39,7 +39,7 @@ from config import config ...@@ -39,7 +39,7 @@ from config import config
from tab import MucTab, InfoTab, PrivateTab, RosterInfoTab, ConversationTab from tab import MucTab, InfoTab, PrivateTab, RosterInfoTab, ConversationTab
from user import User from user import User
from room import Room from room import Room
from roster import Roster, RosterGroup from roster import Roster, RosterGroup, roster
from contact import Contact, Resource from contact import Contact, Resource
from message import Message from message import Message
from text_buffer import TextBuffer from text_buffer import TextBuffer
...@@ -81,7 +81,7 @@ class Gui(object): ...@@ -81,7 +81,7 @@ class Gui(object):
else RosterInfoTab(self.stdscr) else RosterInfoTab(self.stdscr)
default_tab.on_gain_focus() default_tab.on_gain_focus()
self.tabs = [default_tab] self.tabs = [default_tab]
self.roster = Roster() # self.roster = Roster()
# a unique buffer used to store global informations # a unique buffer used to store global informations
# that are displayed in almost all tabs, in an # that are displayed in almost all tabs, in an
# information window. # information window.
...@@ -150,6 +150,8 @@ class Gui(object): ...@@ -150,6 +150,8 @@ class Gui(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):
""" """
""" """
...@@ -172,7 +174,7 @@ class Gui(object): ...@@ -172,7 +174,7 @@ class Gui(object):
def on_got_offline(self, presence): def on_got_offline(self, presence):
jid = presence['from'] jid = presence['from']
contact = self.roster.get_contact_by_jid(jid.bare) contact = roster.get_contact_by_jid(jid.bare)
if not contact: if not contact:
return return
resource = contact.get_resource_by_fulljid(jid.full) resource = contact.get_resource_by_fulljid(jid.full)
...@@ -184,7 +186,7 @@ class Gui(object): ...@@ -184,7 +186,7 @@ class Gui(object):
def on_got_online(self, presence): def on_got_online(self, presence):
jid = presence['from'] jid = presence['from']
contact = self.roster.get_contact_by_jid(jid.bare) contact = roster.get_contact_by_jid(jid.bare)
if not contact: if not contact:
# Todo, handle presence comming from contacts not in roster # Todo, handle presence comming from contacts not in roster
return return
...@@ -471,8 +473,7 @@ class Gui(object): ...@@ -471,8 +473,7 @@ class Gui(object):
""" """
""" """
jid = presence['from'] jid = presence['from']
# contact = ros contact = roster.get_contact_by_jid(jid.bare)
contact = self.roster.get_contact_by_jid(jid.bare)
if not contact: if not contact:
return return
resource = contact.get_resource_by_fulljid(jid.full) resource = contact.get_resource_by_fulljid(jid.full)
...@@ -485,6 +486,34 @@ class Gui(object): ...@@ -485,6 +486,34 @@ class Gui(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
...@@ -492,10 +521,10 @@ class Gui(object): ...@@ -492,10 +521,10 @@ class Gui(object):
""" """
for item in iq.findall('{jabber:iq:roster}query/{jabber:iq:roster}item'): for item in iq.findall('{jabber:iq:roster}query/{jabber:iq:roster}item'):
jid = item.attrib['jid'] jid = item.attrib['jid']
contact = self.roster.get_contact_by_jid(jid) contact = roster.get_contact_by_jid(jid)
if not contact: if not contact:
contact = Contact(jid) contact = Contact(jid)
self.roster.add_contact(contact, jid) roster.add_contact(contact, jid)
if 'ask' in item.attrib: if 'ask' in item.attrib:
contact.set_ask(item.attrib['ask']) contact.set_ask(item.attrib['ask'])
else: else:
...@@ -507,7 +536,7 @@ class Gui(object): ...@@ -507,7 +536,7 @@ class Gui(object):
if item.attrib['subscription']: if item.attrib['subscription']:
contact.set_subscription(item.attrib['subscription']) contact.set_subscription(item.attrib['subscription'])
groups = item.findall('{jabber:iq:roster}group') groups = item.findall('{jabber:iq:roster}group')
self.roster.edit_groups_of_contact(contact, [group.text for group in groups]) roster.edit_groups_of_contact(contact, [group.text for group in groups])
if isinstance(self.current_tab(), RosterInfoTab): if isinstance(self.current_tab(), RosterInfoTab):
self.refresh_window() self.refresh_window()
...@@ -602,7 +631,7 @@ class Gui(object): ...@@ -602,7 +631,7 @@ class Gui(object):
Refresh everything Refresh everything
""" """
self.current_tab().set_color_state(theme.COLOR_TAB_CURRENT) self.current_tab().set_color_state(theme.COLOR_TAB_CURRENT)
self.current_tab().refresh(self.tabs, self.information_buffer, self.roster) self.current_tab().refresh(self.tabs, self.information_buffer, roster)
self.doupdate() self.doupdate()
def open_new_room(self, room, nick, focus=True): def open_new_room(self, room, nick, focus=True):
...@@ -738,7 +767,7 @@ class Gui(object): ...@@ -738,7 +767,7 @@ class Gui(object):
own_nick = room.own_nick own_nick = room.own_nick
r = Room(complete_jid, own_nick) # PrivateRoom here r = Room(complete_jid, own_nick) # PrivateRoom here
new_tab = PrivateTab(self.stdscr, r, self.information_win_size) new_tab = PrivateTab(self.stdscr, r, self.information_win_size)
# insert it in the rooms # insert it in the tabs
if self.current_tab().nb == 0: if self.current_tab().nb == 0:
self.tabs.append(new_tab) self.tabs.append(new_tab)
else: else:
......
...@@ -24,6 +24,9 @@ from contact import Contact, Resource ...@@ -24,6 +24,9 @@ from contact import Contact, Resource
class Roster(object): class Roster(object):
def __init__(self): def __init__(self):
self._contact_filter = None # A tuple(function, *args)
# function to filter contacts,
# on search, for example
self._contacts = {} # key = bare jid; value = Contact() self._contacts = {} # key = bare jid; value = Contact()
self._roster_groups = [] self._roster_groups = []
...@@ -112,7 +115,7 @@ class Roster(object): ...@@ -112,7 +115,7 @@ class Roster(object):
continue continue
length += 1 # One for the group's line itself length += 1 # One for the group's line itself
if not group.folded: if not group.folded:
for contact in group.get_contacts(): for contact in group.get_contacts(self._contact_filter):
# We do not count the offline contacts (depending on config) # We do not count the offline contacts (depending on config)
if config.get('roster_show_offline', 'false') == 'false' and\ if config.get('roster_show_offline', 'false') == 'false' and\
contact.get_nb_resources() == 0: contact.get_nb_resources() == 0:
...@@ -178,7 +181,7 @@ class RosterGroup(object): ...@@ -178,7 +181,7 @@ class RosterGroup(object):
assert contact not in self._contacts assert contact not in self._contacts
self._contacts.append(contact) self._contacts.append(contact)
def get_contacts(self): def get_contacts(self, contact_filter):
def compare_contact(a): def compare_contact(a):
if not a.get_highest_priority_resource(): if not a.get_highest_priority_resource():
return 0 return 0
...@@ -186,7 +189,9 @@ class RosterGroup(object): ...@@ -186,7 +189,9 @@ class RosterGroup(object):
if show not in PRESENCE_PRIORITY: if show not in PRESENCE_PRIORITY:
return 5 return 5
return PRESENCE_PRIORITY[show] return PRESENCE_PRIORITY[show]
return sorted(self._contacts, key=compare_contact, reverse=True) contact_list = self._contacts if not contact_filter\
else [contact for contact in self._contacts if contact_filter[0](contact, contact_filter[1])]
return sorted(contact_list, key=compare_contact, reverse=True)
def toggle_folded(self): def toggle_folded(self):
self.folded = not self.folded self.folded = not self.folded
...@@ -203,3 +208,5 @@ class RosterGroup(object): ...@@ -203,3 +208,5 @@ class RosterGroup(object):
if contact.get_highest_priority_resource(): if contact.get_highest_priority_resource():
l += 1 l += 1
return l return l
roster = Roster()
...@@ -29,7 +29,7 @@ import window ...@@ -29,7 +29,7 @@ import window
import theme import theme
import curses import curses
from config import config from config import config
from roster import RosterGroup from roster import RosterGroup, roster
from contact import Contact, Resource from contact import Contact, Resource
class Tab(object): class Tab(object):
...@@ -395,6 +395,7 @@ class RosterInfoTab(Tab): ...@@ -395,6 +395,7 @@ class RosterInfoTab(Tab):
"KEY_UP": self.move_cursor_up, "KEY_UP": self.move_cursor_up,
"KEY_DOWN": self.move_cursor_down, "KEY_DOWN": self.move_cursor_down,
"o": self.toggle_offline_show, "o": self.toggle_offline_show,
"^F": self.start_search,
} }
Tab.__init__(self, stdscr) Tab.__init__(self, stdscr)
self.name = "Roster" self.name = "Roster"
...@@ -439,11 +440,14 @@ class RosterInfoTab(Tab): ...@@ -439,11 +440,14 @@ class RosterInfoTab(Tab):
def on_input(self, key): def on_input(self, key):
if self.input.input_mode: if self.input.input_mode:
ret = self.input.do_command(key) ret = self.input.do_command(key)
roster._contact_filter = (jid_and_name_match, self.input.text)
# if the input is empty, go back to command mode # if the input is empty, go back to command mode
if self.input.is_empty(): if self.input.is_empty() and not self.input._instructions:
self.input.input_mode = False self.input.input_mode = False
curses.curs_set(0) curses.curs_set(0)
self.input.rewrite_text() self.input.rewrite_text()
if self.input._instructions:
return True
return ret return ret
if key in self.single_key_commands: if key in self.single_key_commands:
return self.single_key_commands[key]() return self.single_key_commands[key]()
...@@ -458,6 +462,7 @@ class RosterInfoTab(Tab): ...@@ -458,6 +462,7 @@ class RosterInfoTab(Tab):
else: else:
config.set_and_save(option, 'false') config.set_and_save(option, 'false')
return True return True
def on_slash(self): def on_slash(self):
""" """
'/' is pressed, we enter "input mode" '/' is pressed, we enter "input mode"
...@@ -501,10 +506,27 @@ class RosterInfoTab(Tab): ...@@ -501,10 +506,27 @@ class RosterInfoTab(Tab):
isinstance(selected_row, Contact): isinstance(selected_row, Contact):
selected_row.toggle_folded() selected_row.toggle_folded()
return True return True
def on_enter(self): def on_enter(self):
selected_row = self.roster_win.get_selected_row() selected_row = self.roster_win.get_selected_row()
return selected_row return selected_row
def start_search(self):
"""
Start the search. The input should appear with a short instruction
in it.
"""
curses.curs_set(1)
roster._contact_filter = (jid_and_name_match, self.input.text)
self.input.input_mode = True
self.input.start_command(self.on_search_terminate, self.on_search_terminate, '[search]')
return True
def on_search_terminate(self, txt):
curses.curs_set(0)
roster._contact_filter = None
return True
def just_before_refresh(self): def just_before_refresh(self):
return return
...@@ -578,3 +600,13 @@ class ConversationTab(Tab): ...@@ -578,3 +600,13 @@ class ConversationTab(Tab):
def just_before_refresh(self): def just_before_refresh(self):
return return
def jid_and_name_match(contact, txt):
"""
A function used to know if a contact in the roster should
be shown in the roster
"""
# TODO: search in nickname, and use libdiff
if txt in contact.get_bare_jid():
return True
return False
...@@ -28,7 +28,7 @@ from config import config ...@@ -28,7 +28,7 @@ from config import config
from threading import Lock from threading import Lock
from contact import Contact, Resource from contact import Contact, Resource
from roster import RosterGroup from roster import RosterGroup, roster
from message import Line from message import Line
from tab import MIN_WIDTH, MIN_HEIGHT from tab import MIN_WIDTH, MIN_HEIGHT
...@@ -546,8 +546,8 @@ class Input(Win): ...@@ -546,8 +546,8 @@ 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.get_text, '^J': self.on_enter,
'\n': self.get_text, '\n': self.on_enter,
} }
Win.__init__(self, height, width, y, x, stdscr) Win.__init__(self, height, width, y, x, stdscr)
...@@ -564,6 +564,51 @@ class Input(Win): ...@@ -564,6 +564,51 @@ class Input(Win):
self.hit_list = [] # current possible completion (normal) self.hit_list = [] # current possible completion (normal)
self.last_completion = None # Contains the last nickname completed, self.last_completion = None # Contains the last nickname completed,
# if last key was a tab # 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
...@@ -881,11 +926,17 @@ class Input(Win): ...@@ -881,11 +926,17 @@ class Input(Win):
with g_lock: with g_lock:
self.clear_text() self.clear_text()
if self.input_mode: if self.input_mode:
self.addstr(self._instructions, curses.color_pair(theme.COLOR_INFORMATION_BAR))
if self._instructions:
self.addstr(' ')
self.addstr(self.text[self.line_pos:self.line_pos+self.width-1]) self.addstr(self.text[self.line_pos:self.line_pos+self.width-1])
else: else:
self.addstr(self.help_text, curses.color_pair(theme.COLOR_INFORMATION_BAR)) self.addstr(self.help_text, curses.color_pair(theme.COLOR_INFORMATION_BAR))
self.finish_line(theme.COLOR_INFORMATION_BAR) self.finish_line(theme.COLOR_INFORMATION_BAR)
self.addstr(0, self.pos, '') # WTF, this works but .move() doesn't… cursor_pos = self.pos
if self._instructions:
cursor_pos += 1 + len(self._instructions)
self.addstr(0, cursor_pos, '') # WTF, this works but .move() doesn't…
self._refresh() self._refresh()
def refresh(self): def refresh(self):
...@@ -992,11 +1043,10 @@ class RosterWin(Win): ...@@ -992,11 +1043,10 @@ class RosterWin(Win):
y += 1 y += 1
if group.folded: if group.folded:
continue continue
for contact in group.get_contacts(): for contact in group.get_contacts(roster._contact_filter):
if config.get('roster_show_offline', 'false') == 'false' and\ if config.get('roster_show_offline', 'false') == 'false' and\
contact.get_nb_resources() == 0: contact.get_nb_resources() == 0:
continue continue
if y-1 == self.pos: if y-1 == self.pos:
self.selected_row = contact self.selected_row = contact
if y-self.start_pos+1 == self.height: if y-self.start_pos+1 == self.height:
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment