summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/contact.py10
-rw-r--r--src/gui.py6
-rw-r--r--src/multiuserchat.py4
-rw-r--r--src/roster.py87
-rw-r--r--src/tab.py66
-rw-r--r--src/user.py1
-rw-r--r--src/window.py27
7 files changed, 159 insertions, 42 deletions
diff --git a/src/contact.py b/src/contact.py
index 5fa0a17d..35a4a4a8 100644
--- a/src/contact.py
+++ b/src/contact.py
@@ -14,6 +14,11 @@
# You should have received a copy of the GNU General Public License
# along with Poezio. If not, see <http://www.gnu.org/licenses/>.
+
+"""
+Defines the Resource and Contact classes
+"""
+
from sleekxmpp.xmlstream.stanzabase import JID
class Resource(object):
@@ -55,7 +60,7 @@ class Resource(object):
class Contact(object):
"""
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
"""
def __init__(self, bare_jid):
@@ -116,6 +121,7 @@ class Contact(object):
if resource.get_jid().full == fulljid:
return resource
return None
+
def toggle_folded(self):
"""
Fold if it's unfolded, and vice versa
@@ -148,7 +154,7 @@ class Contact(object):
def get_resources(self):
"""
- Return all resources
+ Return all resources, sorted by priority
"""
compare_resources = lambda x: x.get_priority()
return sorted(self._resources, key=compare_resources)
diff --git a/src/gui.py b/src/gui.py
index ec7480bd..8e14ca35 100644
--- a/src/gui.py
+++ b/src/gui.py
@@ -171,8 +171,6 @@ class Gui(object):
self.refresh_window()
def on_got_offline(self, presence):
- from common import debug
- debug('OFFLINE: %s\n' % presence)
jid = presence['from']
contact = self.roster.get_contact_by_jid(jid.bare)
if not contact:
@@ -185,8 +183,6 @@ class Gui(object):
self.refresh_window()
def on_got_online(self, presence):
- from common import debug
- debug('ONLINE: %s\n' % presence)
jid = presence['from']
contact = self.roster.get_contact_by_jid(jid.bare)
if not contact:
@@ -537,7 +533,7 @@ class Gui(object):
Resize the whole screen
"""
with resize_lock:
- self.resize_timer = None
+ # self.resize_timer = None
for tab in self.tabs:
tab.resize(self.stdscr)
self.refresh_window()
diff --git a/src/multiuserchat.py b/src/multiuserchat.py
index 5800e7f9..311bce87 100644
--- a/src/multiuserchat.py
+++ b/src/multiuserchat.py
@@ -22,9 +22,6 @@ import sleekxmpp
from xml.etree import cElementTree as ET
-
-from common import debug
-
def send_private_message(xmpp, jid, line):
"""
Send a private message
@@ -54,7 +51,6 @@ def change_show(xmpp, jid, own_nick, show, status):
pres['type'] = show
if status:
pres['status'] = status
- debug('Change presence: %s\n' % (pres))
pres.send()
def change_subject(xmpp, jid, subject):
diff --git a/src/roster.py b/src/roster.py
index 03a5f93a..7191e5ca 100644
--- a/src/roster.py
+++ b/src/roster.py
@@ -14,6 +14,12 @@
# You should have received a copy of the GNU General Public License
# 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
class Roster(object):
@@ -29,9 +35,15 @@ class Roster(object):
self._contacts[jid] = contact
def get_contact_len(self):
+ """
+ Return the number of contacts in this group
+ """
return len(self._contacts.keys())
def get_contact_by_jid(self, jid):
+ """
+ Returns the contact with the given bare JID
+ """
if jid in self._contacts:
return self._contacts[jid]
return None
@@ -42,15 +54,15 @@ class Roster(object):
Add or remove RosterGroup if needed
"""
# 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']
for group in groups:
- if group in contact._groups:
- continue
- else:
+ if group not in contact._groups:
# create the group if it doesn't exist yet
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
for group in contact._groups:
if group not in groups:
@@ -60,7 +72,7 @@ class Roster(object):
def remove_contact_from_group(self, group_name, contact):
"""
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:
if group.name == group_name:
@@ -76,28 +88,40 @@ class Roster(object):
"""
for group in self._roster_groups:
if group.name == group_name:
- group.add_contact(contact)
+ if not group.has_contact(contact):
+ group.add_contact(contact)
return
new_group = RosterGroup(group_name)
self._roster_groups.append(new_group)
new_group.add_contact(contact)
def get_groups(self):
+ """
+ Returns the list of groups
+ """
return self._roster_groups
def __len__(self):
"""
Return the number of line that would be printed
+ for the whole roster
"""
- l = 0
+ length = 0
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:
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:
- l += contact.get_nb_resources()
- return l
+ # One for each resource, if the contact is unfolded
+ length += contact.get_nb_resources()
+ return length
def __repr__(self):
ret = '== Roster:\nContacts:\n'
@@ -108,6 +132,13 @@ class Roster(object):
ret += '%s\n' % (group,)
return ret + '\n'
+PRESENCE_PRIORITY = {'unavailable': 0,
+ 'xa': 1,
+ 'away': 2,
+ 'dnd': 3,
+ '': 4,
+ 'available': 4}
+
class RosterGroup(object):
"""
A RosterGroup is a group containing contacts
@@ -122,6 +153,15 @@ class RosterGroup(object):
def is_empty(self):
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):
"""
Remove a Contact object to the list
@@ -139,10 +179,27 @@ class RosterGroup(object):
self._contacts.append(contact)
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):
return '<Roster_group: %s; %s>' % (self.name, self._contacts)
- def toggle_folded(self):
- self.folded = not self.folded
+ def __len__(self):
+ 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
diff --git a/src/tab.py b/src/tab.py
index 35e71efe..04d55f80 100644
--- a/src/tab.py
+++ b/src/tab.py
@@ -28,6 +28,7 @@ MIN_HEIGHT = 16
import window
import theme
import curses
+from config import config
from roster import RosterGroup
from contact import Contact, Resource
@@ -357,7 +358,10 @@ class PrivateTab(Tab):
def on_gain_focus(self):
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):
self._room.scroll_up(self.text_win.height-1)
@@ -382,6 +386,16 @@ class RosterInfoTab(Tab):
A tab, splitted in two, containing the roster and infos
"""
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)
self.name = "Roster"
roster_width = self.width//2
@@ -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.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.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)
def resize(self, stdscr):
@@ -423,12 +437,34 @@ class RosterInfoTab(Tab):
self._color_state = color
def on_input(self, key):
- if key in ('\n', '^J', '^M') and self.input.is_empty():
- return self.on_enter()
- if key == ' ':
- return self.on_space()
- # In writting mode
- # return self.input.do_command(key)
+ if self.input.input_mode:
+ ret = self.input.do_command(key)
+ # if the input is empty, go back to command mode
+ if self.input.is_empty():
+ self.input.input_mode = False
+ 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):
self._color_state = theme.COLOR_TAB_NORMAL
@@ -440,11 +476,21 @@ class RosterInfoTab(Tab):
def add_message(self):
return False
- def on_scroll_down(self):
+ def move_cursor_down(self):
self.roster_win.move_cursor_down()
+ return True
- def on_scroll_up(self):
+ def move_cursor_up(self):
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, _, __):
pass
diff --git a/src/user.py b/src/user.py
index 1ed2a9f1..b2b8790c 100644
--- a/src/user.py
+++ b/src/user.py
@@ -31,7 +31,6 @@ class User(object):
keep trace of an user in a Room
"""
def __init__(self, nick, affiliation, show, status, role):
- from common import debug
self.last_talked = datetime(1, 1, 1) # The oldest possible time
self.update(affiliation, show, status, role)
self.change_nick(nick)
diff --git a/src/window.py b/src/window.py
index 0e628a8c..eb2c157b 100644
--- a/src/window.py
+++ b/src/window.py
@@ -518,8 +518,11 @@ class TextWin(Win):
class Input(Win):
"""
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 = {
"KEY_LEFT": self.key_left,
"M-D": self.key_left,
@@ -548,6 +551,8 @@ class Input(Win):
}
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.history = []
self.text = ''
@@ -875,8 +880,12 @@ class Input(Win):
"""
with g_lock:
self.clear_text()
- self.addstr(self.text[self.line_pos:self.line_pos+self.width-1])
- self.addstr(0, self.pos, '') # WTF, this works but .move() doesn't...
+ if self.input_mode:
+ 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()
def refresh(self):
@@ -928,7 +937,7 @@ class RosterWin(Win):
'chat':theme.COLOR_STATUS_CHAT,
'unavailable':theme.COLOR_STATUS_UNAVAILABLE
}
- # subscription_char = {'both': '
+
def __init__(self, height, width, y, x, parent_win, visible):
self.visible = visible
Win.__init__(self, height, width, y, x, parent_win)
@@ -967,10 +976,14 @@ class RosterWin(Win):
return
with g_lock:
self.roster_len = len(roster)
+ while self.roster_len and self.pos >= self.roster_len:
+ self.move_cursor_up()
self._win.erase()
self.draw_roster_information(roster)
y = 1
for group in roster.get_groups():
+ if group.get_nb_connected_contacts() == 0:
+ continue # Ignore empty groups
# This loop is really REALLY ugly :^)
if y-1 == self.pos:
self.selected_row = group
@@ -980,6 +993,10 @@ class RosterWin(Win):
if group.folded:
continue
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:
self.selected_row = contact
if y-self.start_pos+1 == self.height:
@@ -1007,7 +1024,7 @@ class RosterWin(Win):
def draw_plus(self, y):
"""
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))