Command mode in roster tab, toggle offline contacts with 'o' and sort contacts by show

parent d6e37883
...@@ -14,6 +14,11 @@ ...@@ -14,6 +14,11 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with Poezio. If not, see <http://www.gnu.org/licenses/>. # along with Poezio. If not, see <http://www.gnu.org/licenses/>.
"""
Defines the Resource and Contact classes
"""
from sleekxmpp.xmlstream.stanzabase import JID from sleekxmpp.xmlstream.stanzabase import JID
class Resource(object): class Resource(object):
...@@ -55,7 +60,7 @@ class Resource(object): ...@@ -55,7 +60,7 @@ class Resource(object):
class Contact(object): class Contact(object):
""" """
This a way to gather multiple resources from the same bare JID. This a way to gather multiple resources from the same bare JID.
This class contains zero or more esource class and useful methods This class contains zero or more Resource object and useful methods
to get the resource with the highest priority, etc to get the resource with the highest priority, etc
""" """
def __init__(self, bare_jid): def __init__(self, bare_jid):
...@@ -116,6 +121,7 @@ class Contact(object): ...@@ -116,6 +121,7 @@ class Contact(object):
if resource.get_jid().full == fulljid: if resource.get_jid().full == fulljid:
return resource return resource
return None return None
def toggle_folded(self): def toggle_folded(self):
""" """
Fold if it's unfolded, and vice versa Fold if it's unfolded, and vice versa
...@@ -148,7 +154,7 @@ class Contact(object): ...@@ -148,7 +154,7 @@ class Contact(object):
def get_resources(self): def get_resources(self):
""" """
Return all resources Return all resources, sorted by priority
""" """
compare_resources = lambda x: x.get_priority() compare_resources = lambda x: x.get_priority()
return sorted(self._resources, key=compare_resources) return sorted(self._resources, key=compare_resources)
......
...@@ -171,8 +171,6 @@ class Gui(object): ...@@ -171,8 +171,6 @@ class Gui(object):
self.refresh_window() self.refresh_window()
def on_got_offline(self, presence): def on_got_offline(self, presence):
from common import debug
debug('OFFLINE: %s\n' % presence)
jid = presence['from'] jid = presence['from']
contact = self.roster.get_contact_by_jid(jid.bare) contact = self.roster.get_contact_by_jid(jid.bare)
if not contact: if not contact:
...@@ -185,8 +183,6 @@ class Gui(object): ...@@ -185,8 +183,6 @@ class Gui(object):
self.refresh_window() self.refresh_window()
def on_got_online(self, presence): def on_got_online(self, presence):
from common import debug
debug('ONLINE: %s\n' % presence)
jid = presence['from'] jid = presence['from']
contact = self.roster.get_contact_by_jid(jid.bare) contact = self.roster.get_contact_by_jid(jid.bare)
if not contact: if not contact:
...@@ -537,7 +533,7 @@ class Gui(object): ...@@ -537,7 +533,7 @@ class Gui(object):
Resize the whole screen Resize the whole screen
""" """
with resize_lock: with resize_lock:
self.resize_timer = None # self.resize_timer = None
for tab in self.tabs: for tab in self.tabs:
tab.resize(self.stdscr) tab.resize(self.stdscr)
self.refresh_window() self.refresh_window()
......
...@@ -22,9 +22,6 @@ import sleekxmpp ...@@ -22,9 +22,6 @@ import sleekxmpp
from xml.etree import cElementTree as ET from xml.etree import cElementTree as ET
from common import debug
def send_private_message(xmpp, jid, line): def send_private_message(xmpp, jid, line):
""" """
Send a private message Send a private message
...@@ -54,7 +51,6 @@ def change_show(xmpp, jid, own_nick, show, status): ...@@ -54,7 +51,6 @@ def change_show(xmpp, jid, own_nick, show, status):
pres['type'] = show pres['type'] = show
if status: if status:
pres['status'] = status pres['status'] = status
debug('Change presence: %s\n' % (pres))
pres.send() pres.send()
def change_subject(xmpp, jid, subject): def change_subject(xmpp, jid, subject):
......
...@@ -14,6 +14,12 @@ ...@@ -14,6 +14,12 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with Poezio. If not, see <http://www.gnu.org/licenses/>. # along with Poezio. If not, see <http://www.gnu.org/licenses/>.
"""
Defines the Roster and RosterGroup classes
"""
from config import config
from contact import Contact, Resource from contact import Contact, Resource
class Roster(object): class Roster(object):
...@@ -29,9 +35,15 @@ class Roster(object): ...@@ -29,9 +35,15 @@ class Roster(object):
self._contacts[jid] = contact self._contacts[jid] = contact
def get_contact_len(self): def get_contact_len(self):
"""
Return the number of contacts in this group
"""
return len(self._contacts.keys()) return len(self._contacts.keys())
def get_contact_by_jid(self, jid): def get_contact_by_jid(self, jid):
"""
Returns the contact with the given bare JID
"""
if jid in self._contacts: if jid in self._contacts:
return self._contacts[jid] return self._contacts[jid]
return None return None
...@@ -42,15 +54,15 @@ class Roster(object): ...@@ -42,15 +54,15 @@ class Roster(object):
Add or remove RosterGroup if needed Add or remove RosterGroup if needed
""" """
# add the contact to each group he is in # add the contact to each group he is in
if not len(groups): # If the contact hasn't any group, we put her in
# the virtual default 'none' group
if not len(groups):
groups = ['none'] groups = ['none']
for group in groups: for group in groups:
if group in contact._groups: if group not in contact._groups:
continue
else:
# create the group if it doesn't exist yet # create the group if it doesn't exist yet
contact._groups.append(group) contact._groups.append(group)
self.add_contact_to_group(group, contact) self.add_contact_to_group(group, contact)
# remove the contact from each group he is not in # remove the contact from each group he is not in
for group in contact._groups: for group in contact._groups:
if group not in groups: if group not in groups:
...@@ -60,7 +72,7 @@ class Roster(object): ...@@ -60,7 +72,7 @@ class Roster(object):
def remove_contact_from_group(self, group_name, contact): def remove_contact_from_group(self, group_name, contact):
""" """
Remove the contact from the group. Remove the contact from the group.
Remove also the group if this makes it empty Delete the group if this makes it empty
""" """
for group in self._roster_groups: for group in self._roster_groups:
if group.name == group_name: if group.name == group_name:
...@@ -76,28 +88,40 @@ class Roster(object): ...@@ -76,28 +88,40 @@ class Roster(object):
""" """
for group in self._roster_groups: for group in self._roster_groups:
if group.name == group_name: if group.name == group_name:
group.add_contact(contact) if not group.has_contact(contact):
group.add_contact(contact)
return return
new_group = RosterGroup(group_name) new_group = RosterGroup(group_name)
self._roster_groups.append(new_group) self._roster_groups.append(new_group)
new_group.add_contact(contact) new_group.add_contact(contact)
def get_groups(self): def get_groups(self):
"""
Returns the list of groups
"""
return self._roster_groups return self._roster_groups
def __len__(self): def __len__(self):
""" """
Return the number of line that would be printed Return the number of line that would be printed
for the whole roster
""" """
l = 0 length = 0
for group in self._roster_groups: for group in self._roster_groups:
l += 1 if group.get_nb_connected_contacts() == 0:
continue
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():
l += 1 # We do not count the offline contacts (depending on config)
if config.get('roster_show_offline', 'false') == 'false' and\
contact.get_nb_resources() == 0:
continue
length += 1 # One for the contact's line
if not contact._folded: if not contact._folded:
l += contact.get_nb_resources() # One for each resource, if the contact is unfolded
return l length += contact.get_nb_resources()
return length
def __repr__(self): def __repr__(self):
ret = '== Roster:\nContacts:\n' ret = '== Roster:\nContacts:\n'
...@@ -108,6 +132,13 @@ class Roster(object): ...@@ -108,6 +132,13 @@ class Roster(object):
ret += '%s\n' % (group,) ret += '%s\n' % (group,)
return ret + '\n' return ret + '\n'
PRESENCE_PRIORITY = {'unavailable': 0,
'xa': 1,
'away': 2,
'dnd': 3,
'': 4,
'available': 4}
class RosterGroup(object): class RosterGroup(object):
""" """
A RosterGroup is a group containing contacts A RosterGroup is a group containing contacts
...@@ -122,6 +153,15 @@ class RosterGroup(object): ...@@ -122,6 +153,15 @@ class RosterGroup(object):
def is_empty(self): def is_empty(self):
return len(self._contacts) == 0 return len(self._contacts) == 0
def has_contact(self, contact):
"""
Return a bool, telling if the contact
is already in the group
"""
if contact in self._contacts:
return True
return False
def remove_contact(self, contact): def remove_contact(self, contact):
""" """
Remove a Contact object to the list Remove a Contact object to the list
...@@ -139,10 +179,27 @@ class RosterGroup(object): ...@@ -139,10 +179,27 @@ class RosterGroup(object):
self._contacts.append(contact) self._contacts.append(contact)
def get_contacts(self): def get_contacts(self):
return self._contacts def compare_contact(a):
if not a.get_highest_priority_resource():
return 0
show = a.get_highest_priority_resource().get_presence()
if show not in PRESENCE_PRIORITY:
return 5
return PRESENCE_PRIORITY[show]
return sorted(self._contacts, key=compare_contact, reverse=True)
def toggle_folded(self):
self.folded = not self.folded
def __repr__(self): def __repr__(self):
return '<Roster_group: %s; %s>' % (self.name, self._contacts) return '<Roster_group: %s; %s>' % (self.name, self._contacts)
def toggle_folded(self): def __len__(self):
self.folded = not self.folded return len(self._contacts)
def get_nb_connected_contacts(self):
l = 0
for contact in self._contacts:
if contact.get_highest_priority_resource():
l += 1
return l
...@@ -28,6 +28,7 @@ MIN_HEIGHT = 16 ...@@ -28,6 +28,7 @@ MIN_HEIGHT = 16
import window import window
import theme import theme
import curses import curses
from config import config
from roster import RosterGroup from roster import RosterGroup
from contact import Contact, Resource from contact import Contact, Resource
...@@ -357,7 +358,10 @@ class PrivateTab(Tab): ...@@ -357,7 +358,10 @@ class PrivateTab(Tab):
def on_gain_focus(self): def on_gain_focus(self):
self._room.set_color_state(theme.COLOR_TAB_CURRENT) self._room.set_color_state(theme.COLOR_TAB_CURRENT)
curses.curs_set(1) if not self.input.input_mode:
curses.curs_set(1)
else:
curses.curs_set(0)
def on_scroll_up(self): def on_scroll_up(self):
self._room.scroll_up(self.text_win.height-1) self._room.scroll_up(self.text_win.height-1)
...@@ -382,6 +386,16 @@ class RosterInfoTab(Tab): ...@@ -382,6 +386,16 @@ 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): def __init__(self, stdscr):
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,
}
Tab.__init__(self, stdscr) Tab.__init__(self, stdscr)
self.name = "Roster" self.name = "Roster"
roster_width = self.width//2 roster_width = self.width//2
...@@ -391,7 +405,7 @@ class RosterInfoTab(Tab): ...@@ -391,7 +405,7 @@ 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) self.input = window.Input(1, self.width, self.height-1, 0, stdscr, self.visible, False, "Enter commands with “/”. “o”: toggle offline show")
self.set_color_state(theme.COLOR_TAB_NORMAL) self.set_color_state(theme.COLOR_TAB_NORMAL)
def resize(self, stdscr): def resize(self, stdscr):
...@@ -423,12 +437,34 @@ class RosterInfoTab(Tab): ...@@ -423,12 +437,34 @@ class RosterInfoTab(Tab):
self._color_state = color self._color_state = color
def on_input(self, key): def on_input(self, key):
if key in ('\n', '^J', '^M') and self.input.is_empty(): if self.input.input_mode:
return self.on_enter() ret = self.input.do_command(key)
if key == ' ': # if the input is empty, go back to command mode
return self.on_space() if self.input.is_empty():
# In writting mode self.input.input_mode = False
# return self.input.do_command(key) curses.curs_set(0)
self.input.rewrite_text()
return ret
if key in self.single_key_commands:
return self.single_key_commands[key]()
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
def on_slash(self):
"""
'/' is pressed, we enter "input mode"
"""
self.input.input_mode = True
curses.curs_set(1)
self.on_input("/") # we add the slash
def on_lose_focus(self): def on_lose_focus(self):
self._color_state = theme.COLOR_TAB_NORMAL self._color_state = theme.COLOR_TAB_NORMAL
...@@ -440,11 +476,21 @@ class RosterInfoTab(Tab): ...@@ -440,11 +476,21 @@ class RosterInfoTab(Tab):
def add_message(self): def add_message(self):
return False return False
def on_scroll_down(self): def move_cursor_down(self):
self.roster_win.move_cursor_down() self.roster_win.move_cursor_down()
return True
def on_scroll_up(self): def move_cursor_up(self):
self.roster_win.move_cursor_up() self.roster_win.move_cursor_up()
return True
def on_scroll_down(self):
# Scroll info win
pass
def on_scroll_up(self):
# Scroll info down
pass
def on_info_win_size_changed(self, _, __): def on_info_win_size_changed(self, _, __):
pass pass
......
...@@ -31,7 +31,6 @@ class User(object): ...@@ -31,7 +31,6 @@ class User(object):
keep trace of an user in a Room keep trace of an user in a Room
""" """
def __init__(self, nick, affiliation, show, status, role): def __init__(self, nick, affiliation, show, status, role):
from common import debug
self.last_talked = datetime(1, 1, 1) # The oldest possible time self.last_talked = datetime(1, 1, 1) # The oldest possible time
self.update(affiliation, show, status, role) self.update(affiliation, show, status, role)
self.change_nick(nick) self.change_nick(nick)
......
...@@ -518,8 +518,11 @@ class TextWin(Win): ...@@ -518,8 +518,11 @@ class TextWin(Win):
class Input(Win): class Input(Win):
""" """
The line where text is entered The line where text is entered
It can be in input mode or in commmand mode.
Command mode means that single_key_commands can be entered, handled
by the Tab object, while this input just displays an help text.
""" """
def __init__(self, height, width, y, x, stdscr, visible): def __init__(self, height, width, y, x, stdscr, visible, input_mode=True, help_text=''):
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,
...@@ -548,6 +551,8 @@ class Input(Win): ...@@ -548,6 +551,8 @@ class Input(Win):
} }
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.history = []
self.text = '' self.text = ''
...@@ -875,8 +880,12 @@ class Input(Win): ...@@ -875,8 +880,12 @@ class Input(Win):
""" """
with g_lock: with g_lock:
self.clear_text() self.clear_text()
self.addstr(self.text[self.line_pos:self.line_pos+self.width-1]) if self.input_mode:
self.addstr(0, self.pos, '') # WTF, this works but .move() doesn't... 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…
self._refresh() self._refresh()
def refresh(self): def refresh(self):
...@@ -928,7 +937,7 @@ class RosterWin(Win): ...@@ -928,7 +937,7 @@ class RosterWin(Win):
'chat':theme.COLOR_STATUS_CHAT, 'chat':theme.COLOR_STATUS_CHAT,
'unavailable':theme.COLOR_STATUS_UNAVAILABLE 'unavailable':theme.COLOR_STATUS_UNAVAILABLE
} }
# subscription_char = {'both': '
def __init__(self, height, width, y, x, parent_win, visible): def __init__(self, height, width, y, x, parent_win, visible):
self.visible = visible self.visible = visible
Win.__init__(self, height, width, y, x, parent_win) Win.__init__(self, height, width, y, x, parent_win)
...@@ -967,10 +976,14 @@ class RosterWin(Win): ...@@ -967,10 +976,14 @@ class RosterWin(Win):
return return
with g_lock: with g_lock:
self.roster_len = len(roster) self.roster_len = len(roster)
while self.roster_len and self.pos >= self.roster_len:
self.move_cursor_up()
self._win.erase() self._win.erase()
self.draw_roster_information(roster) self.draw_roster_information(roster)
y = 1 y = 1
for group in roster.get_groups(): for group in roster.get_groups():
if group.get_nb_connected_contacts() == 0:
continue # Ignore empty groups
# This loop is really REALLY ugly :^) # This loop is really REALLY ugly :^)
if y-1 == self.pos: if y-1 == self.pos:
self.selected_row = group self.selected_row = group
...@@ -980,6 +993,10 @@ class RosterWin(Win): ...@@ -980,6 +993,10 @@ class RosterWin(Win):
if group.folded: if group.folded:
continue continue
for contact in group.get_contacts(): for contact in group.get_contacts():
if config.get('roster_show_offline', 'false') == 'false' and\
contact.get_nb_resources() == 0:
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:
...@@ -1007,7 +1024,7 @@ class RosterWin(Win): ...@@ -1007,7 +1024,7 @@ class RosterWin(Win):
def draw_plus(self, y): def draw_plus(self, y):
""" """
Draw the indicator that shows that Draw the indicator that shows that
the list is longer that what is displayed the list is longer than what is displayed
""" """
self.addstr(y, self.width-5, '++++', curses.color_pair(42)) self.addstr(y, self.width-5, '++++', curses.color_pair(42))
......
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