From 6fab04a6dc6dd673a07830185b932d97ff55913c Mon Sep 17 00:00:00 2001 From: "louiz@4325f9fc-e183-4c21-96ce-0ab188b42d13" Date: Sun, 31 Oct 2010 18:57:48 +0000 Subject: Basic search in the roster (based on contact JIDs) --- src/gui.py | 51 +++++++++++++++++++++++++++++++++++++----------- src/roster.py | 13 ++++++++++--- src/tab.py | 36 ++++++++++++++++++++++++++++++++-- src/window.py | 62 +++++++++++++++++++++++++++++++++++++++++++++++++++++------ 4 files changed, 140 insertions(+), 22 deletions(-) diff --git a/src/gui.py b/src/gui.py index 7b60b4f1..f1d5eb0c 100644 --- a/src/gui.py +++ b/src/gui.py @@ -39,7 +39,7 @@ from config import config from tab import MucTab, InfoTab, PrivateTab, RosterInfoTab, ConversationTab from user import User from room import Room -from roster import Roster, RosterGroup +from roster import Roster, RosterGroup, roster from contact import Contact, Resource from message import Message from text_buffer import TextBuffer @@ -81,7 +81,7 @@ class Gui(object): else RosterInfoTab(self.stdscr) default_tab.on_gain_focus() self.tabs = [default_tab] - self.roster = Roster() + # self.roster = Roster() # a unique buffer used to store global informations # that are displayed in almost all tabs, in an # information window. @@ -150,6 +150,8 @@ class Gui(object): self.xmpp.add_event_handler("roster_update", self.on_roster_update) self.xmpp.add_event_handler("changed_status", self.on_presence) + # self.__debug_fill_roster() + def grow_information_win(self): """ """ @@ -172,7 +174,7 @@ class Gui(object): def on_got_offline(self, presence): jid = presence['from'] - contact = self.roster.get_contact_by_jid(jid.bare) + contact = roster.get_contact_by_jid(jid.bare) if not contact: return resource = contact.get_resource_by_fulljid(jid.full) @@ -184,7 +186,7 @@ class Gui(object): def on_got_online(self, presence): jid = presence['from'] - contact = self.roster.get_contact_by_jid(jid.bare) + contact = roster.get_contact_by_jid(jid.bare) if not contact: # Todo, handle presence comming from contacts not in roster return @@ -471,8 +473,7 @@ class Gui(object): """ """ jid = presence['from'] - # contact = ros - contact = self.roster.get_contact_by_jid(jid.bare) + contact = roster.get_contact_by_jid(jid.bare) if not contact: return resource = contact.get_resource_by_fulljid(jid.full) @@ -485,6 +486,34 @@ class Gui(object): if isinstance(self.current_tab(), RosterInfoTab): 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): """ A subscription changed, or we received a roster item @@ -492,10 +521,10 @@ class Gui(object): """ for item in iq.findall('{jabber:iq:roster}query/{jabber:iq:roster}item'): jid = item.attrib['jid'] - contact = self.roster.get_contact_by_jid(jid) + contact = roster.get_contact_by_jid(jid) if not contact: contact = Contact(jid) - self.roster.add_contact(contact, jid) + roster.add_contact(contact, jid) if 'ask' in item.attrib: contact.set_ask(item.attrib['ask']) else: @@ -507,7 +536,7 @@ class Gui(object): if item.attrib['subscription']: contact.set_subscription(item.attrib['subscription']) 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): self.refresh_window() @@ -602,7 +631,7 @@ class Gui(object): Refresh everything """ 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() def open_new_room(self, room, nick, focus=True): @@ -738,7 +767,7 @@ class Gui(object): own_nick = room.own_nick r = Room(complete_jid, own_nick) # PrivateRoom here 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: self.tabs.append(new_tab) else: diff --git a/src/roster.py b/src/roster.py index 7191e5ca..09bc7b6b 100644 --- a/src/roster.py +++ b/src/roster.py @@ -24,6 +24,9 @@ from contact import Contact, Resource class Roster(object): 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._roster_groups = [] @@ -112,7 +115,7 @@ class Roster(object): continue length += 1 # One for the group's line itself 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) if config.get('roster_show_offline', 'false') == 'false' and\ contact.get_nb_resources() == 0: @@ -178,7 +181,7 @@ class RosterGroup(object): assert contact not in self._contacts self._contacts.append(contact) - def get_contacts(self): + def get_contacts(self, contact_filter): def compare_contact(a): if not a.get_highest_priority_resource(): return 0 @@ -186,7 +189,9 @@ class RosterGroup(object): if show not in PRESENCE_PRIORITY: return 5 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): self.folded = not self.folded @@ -203,3 +208,5 @@ class RosterGroup(object): if contact.get_highest_priority_resource(): l += 1 return l + +roster = Roster() diff --git a/src/tab.py b/src/tab.py index 04d55f80..e6a8cc41 100644 --- a/src/tab.py +++ b/src/tab.py @@ -29,7 +29,7 @@ import window import theme import curses from config import config -from roster import RosterGroup +from roster import RosterGroup, roster from contact import Contact, Resource class Tab(object): @@ -395,6 +395,7 @@ class RosterInfoTab(Tab): "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) self.name = "Roster" @@ -439,11 +440,14 @@ class RosterInfoTab(Tab): def on_input(self, key): if self.input.input_mode: 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 self.input.is_empty(): + if self.input.is_empty() and not self.input._instructions: self.input.input_mode = False curses.curs_set(0) self.input.rewrite_text() + if self.input._instructions: + return True return ret if key in self.single_key_commands: return self.single_key_commands[key]() @@ -458,6 +462,7 @@ class RosterInfoTab(Tab): else: config.set_and_save(option, 'false') return True + def on_slash(self): """ '/' is pressed, we enter "input mode" @@ -501,10 +506,27 @@ class RosterInfoTab(Tab): isinstance(selected_row, Contact): selected_row.toggle_folded() return True + def on_enter(self): selected_row = self.roster_win.get_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): return @@ -578,3 +600,13 @@ class ConversationTab(Tab): def just_before_refresh(self): 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 diff --git a/src/window.py b/src/window.py index eb2c157b..1c3f32d9 100644 --- a/src/window.py +++ b/src/window.py @@ -28,7 +28,7 @@ from config import config from threading import Lock from contact import Contact, Resource -from roster import RosterGroup +from roster import RosterGroup, roster from message import Line from tab import MIN_WIDTH, MIN_HEIGHT @@ -546,8 +546,8 @@ class Input(Win): 'M-f': self.jump_word_right, "KEY_BACKSPACE": self.key_backspace, '^?': self.key_backspace, - '^J': self.get_text, - '\n': self.get_text, + '^J': self.on_enter, + '\n': self.on_enter, } Win.__init__(self, height, width, y, x, stdscr) @@ -564,6 +564,51 @@ class Input(Win): 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): return len(self.text) == 0 @@ -881,11 +926,17 @@ class Input(Win): with g_lock: self.clear_text() 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]) else: self.addstr(self.help_text, curses.color_pair(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() def refresh(self): @@ -992,11 +1043,10 @@ class RosterWin(Win): y += 1 if group.folded: 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\ contact.get_nb_resources() == 0: continue - if y-1 == self.pos: self.selected_row = contact if y-self.start_pos+1 == self.height: -- cgit v1.2.3