summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/core.py126
-rw-r--r--src/tabs.py (renamed from src/tab.py)114
-rw-r--r--src/windows.py135
3 files changed, 292 insertions, 83 deletions
diff --git a/src/core.py b/src/core.py
index 8a5b4753..5e7ddaea 100644
--- a/src/core.py
+++ b/src/core.py
@@ -39,9 +39,10 @@ from sleekxmpp.xmlstream.stanzabase import JID
log = logging.getLogger(__name__)
import multiuserchat as muc
+import tabs
+
from connection import connection
from config import config
-from tab import MucTab, InfoTab, PrivateTab, RosterInfoTab, ConversationTab
from logger import logger
from user import User
from room import Room
@@ -83,8 +84,8 @@ class Core(object):
self.stdscr = curses.initscr()
self.init_curses(self.stdscr)
self.xmpp = xmpp
- default_tab = InfoTab(self, "Info") if self.xmpp.anon\
- else RosterInfoTab(self)
+ default_tab = tabs.InfoTab(self, "Info") if self.xmpp.anon\
+ else tabs.RosterInfoTab(self)
default_tab.on_gain_focus()
self.tabs = [default_tab]
# a unique buffer used to store global informations
@@ -183,7 +184,7 @@ class Core(object):
assert resource
self.information('%s is offline' % (resource.get_jid()), "Roster")
contact.remove_resource(resource)
- if isinstance(self.current_tab(), RosterInfoTab):
+ if isinstance(self.current_tab(), tabs.RosterInfoTab):
self.refresh_window()
def on_got_online(self, presence):
@@ -315,7 +316,7 @@ class Core(object):
room.own_nick = new_nick
# also change our nick in all private discussion of this room
for _tab in self.tabs:
- if isinstance(_tab, PrivateTab) and _tab.get_name().split('/', 1)[0] == room.name:
+ if isinstance(_tab, tabs.PrivateTab) and _tab.get_name().split('/', 1)[0] == room.name:
_tab.get_room().own_nick = new_nick
user.change_nick(new_nick)
self.add_message_to_text_buffer(room, _('"[%(old)s]" is now known as "[%(new)s]"') % {'old':from_nick.replace('"', '\\"'), 'new':new_nick.replace('"', '\\"')}, colorized=True)
@@ -497,7 +498,7 @@ class Core(object):
resource.set_presence(status)
resource.set_priority(priority)
resource.set_status(status_message)
- if isinstance(self.current_tab(), RosterInfoTab):
+ if isinstance(self.current_tab(), tabs.RosterInfoTab):
self.refresh_window()
def on_roster_update(self, iq):
@@ -523,7 +524,7 @@ class Core(object):
contact.set_subscription(item.attrib['subscription'])
groups = item.findall('{jabber:iq:roster}group')
roster.edit_groups_of_contact(contact, [group.text for group in groups])
- if isinstance(self.current_tab(), RosterInfoTab):
+ if isinstance(self.current_tab(), tabs.RosterInfoTab):
self.refresh_window()
def call_for_resize(self):
@@ -578,7 +579,7 @@ class Core(object):
Return the room of the ConversationTab with the given jid
"""
for tab in self.tabs:
- if isinstance(tab, ConversationTab):
+ if isinstance(tab, tabs.ConversationTab):
if tab.get_name() == jid:
return tab.get_room()
return None
@@ -597,8 +598,8 @@ class Core(object):
returns the room that has this name
"""
for tab in self.tabs:
- if (isinstance(tab, MucTab) or
- isinstance(tab, PrivateTab)) and tab.get_name() == name:
+ if (isinstance(tab, tabs.MucTab) or
+ isinstance(tab, tabs.PrivateTab)) and tab.get_name() == name:
return tab.get_room()
return None
@@ -629,12 +630,11 @@ class Core(object):
self.current_tab().refresh(self.tabs, self.information_buffer, roster)
self.doupdate()
- def open_new_room(self, room, nick, focus=True):
+ def add_tab(self, new_tab, focus=False):
"""
- Open a new MucTab containing a muc Room, using the specified nick
+ Appends the new_tab in the tab list and
+ focus it if focus==True
"""
- r = Room(room, nick)
- new_tab = MucTab(self, r)
if self.current_tab().nb == 0:
self.tabs.append(new_tab)
else:
@@ -644,6 +644,14 @@ class Core(object):
break
if focus:
self.command_win("%s" % new_tab.nb)
+
+ def open_new_room(self, room, nick, focus=True):
+ """
+ Open a new tab.MucTab containing a muc Room, using the specified nick
+ """
+ r = Room(room, nick)
+ new_tab = tabs.MucTab(self, r)
+ self.add_tab(new_tab, focus)
self.refresh_window()
def go_to_roster(self):
@@ -671,6 +679,10 @@ class Core(object):
if tab.get_color_state() == theme.COLOR_TAB_NEW_MESSAGE:
self.command_win('%s' % tab.nb)
return
+ for tab in self.tabs:
+ if isinstance(tab, tabs.ChatTab) and not tab.input.is_empty():
+ self.command_win('%s' % tab.nb)
+ return
def rotate_rooms_right(self, args=None):
"""
@@ -733,24 +745,16 @@ class Core(object):
open a new conversation tab and focus it if needed
"""
text_buffer = TextBuffer()
- new_tab = ConversationTab(self, text_buffer, jid)
+ new_tab = tabs.ConversationTab(self, text_buffer, jid)
# insert it in the rooms
- if self.current_tab().nb == 0:
- self.tabs.append(new_tab)
- else:
- for ta in self.tabs:
- if ta.nb == 0:
- self.tabs.insert(self.tabs.index(ta), new_tab)
- break
- if focus: # focus the room if needed
- self.command_win('%s' % (new_tab.nb))
+ self.add_tab(new_tab, focus)
self.refresh_window()
return new_tab
def open_private_window(self, room_name, user_nick, focus=True):
complete_jid = room_name+'/'+user_nick
for tab in self.tabs: # if the room exists, focus it and return
- if isinstance(tab, PrivateTab):
+ if isinstance(tab, tabs.PrivateTab):
if tab.get_name() == complete_jid:
self.command_win('%s' % tab.nb)
return
@@ -760,17 +764,9 @@ class Core(object):
return None
own_nick = room.own_nick
r = Room(complete_jid, own_nick) # PrivateRoom here
- new_tab = PrivateTab(self, r)
+ new_tab = tabs.PrivateTab(self, r)
# insert it in the tabs
- if self.current_tab().nb == 0:
- self.tabs.append(new_tab)
- else:
- for ta in self.tabs:
- if ta.nb == 0:
- self.tabs.insert(self.tabs.index(ta), new_tab)
- break
- if focus: # focus the room if needed
- self.command_win('%s' % (new_tab.nb))
+ self.add_tab(new_tab, focus)
# self.window.new_room(r)
self.refresh_window()
return r
@@ -860,6 +856,29 @@ class Core(object):
msg = _('Unknown command: %s') % args[0]
self.information(msg)
+ def command_list(self, arg):
+ """
+ /list <server>
+ Opens a MucListTab containing the list of the room in the specified server
+ """
+ args = arg.split()
+ if len(args) > 1:
+ self.command_help('list')
+ return
+ elif len(args) == 0:
+ if not isinstance(self.current_tab(), tabs.MucTab):
+ return self.information('Warning: Please provide a server')
+ server = JID(self.current_tab().get_name()).server
+ else:
+ server = arg.strip()
+ list_tab = tabs.MucListTab(self, server)
+ self.add_tab(list_tab, True)
+ res = self.xmpp.plugin['xep_0030'].getItems(server)
+ items = [{'node-part':JID(item[0]).user,
+ 'jid': item[0],
+ 'name': item[2]} for item in res['disco_items'].getItems()]
+ list_tab.listview.add_lines(items)
+
def command_whois(self, arg):
"""
/whois <nickname>
@@ -948,29 +967,18 @@ class Core(object):
serv = jid.server
serv_list = []
for tab in self.tabs:
- if isinstance(tab, MucTab):
+ if isinstance(tab, tabs.MucTab):
serv_list.append('%s@%s'% (jid.user, JID(tab.get_name()).host))
the_input.auto_completion(serv_list, '')
return True
- def command_list(self, arg):
- """
- Opens a MucListTab for the specified server
- """
- args = arg.split()
- if len(args) != 1:
- self.command_win('list')
- return
- server = args[1]
- # TODO
-
def completion_list(self, the_input):
"""
"""
txt = the_input.get_text()
muc_serv_list = []
for tab in self.tabs: # TODO, also from an history
- if isinstance(tab, MucTab) and\
+ if isinstance(tab, tabs.MucTab) and\
tab.get_name() not in muc_serv_list:
muc_serv_list.append(tab.get_name())
if muc_serv_list:
@@ -984,7 +992,7 @@ class Core(object):
password = None
if len(args) == 0:
t = self.current_tab()
- if not isinstance(t, MucTab) and not isinstance(t, PrivateTab):
+ if not isinstance(t, tabs.MucTab) and not isinstance(t, tabs.PrivateTab):
return
room = t.get_name()
nick = t.get_room().own_nick
@@ -999,7 +1007,7 @@ class Core(object):
nick = info[1]
if info[0] == '': # happens with /join /nickname, which is OK
t = self.current_tab()
- if not isinstance(t, MucTab):
+ if not isinstance(t, tabs.MucTab):
return
room = t.get_name()
if nick == '':
@@ -1009,7 +1017,7 @@ class Core(object):
if not is_jid(room): # no server is provided, like "/join hello"
# use the server of the current room if available
# check if the current room's name has a server
- if isinstance(self.current_tab(), MucTab) and\
+ if isinstance(self.current_tab(), tabs.MucTab) and\
is_jid(self.current_tab().get_name()):
room += '@%s' % jid_get_domain(self.current_tab().get_name())
else: # no server could be found, print a message and return
@@ -1037,7 +1045,7 @@ class Core(object):
"""
args = arg.split()
nick = None
- if not isinstance(self.current_tab(), MucTab):
+ if not isinstance(self.current_tab(), tabs.MucTab):
return
if len(args) == 0:
room = self.current_tab().get_room()
@@ -1115,7 +1123,7 @@ class Core(object):
else:
msg = None
for tab in self.tabs:
- if isinstance(tab, MucTab) and tab.get_room().joined:
+ if isinstance(tab, tabs.MucTab) and tab.get_room().joined:
muc.change_show(self.xmpp, tab.get_room().name, tab.get_room().own_nick, show, msg)
def command_away(self, arg):
@@ -1141,8 +1149,8 @@ class Core(object):
Close the given tab. If None, close the current one
"""
tab = tab or self.current_tab()
- if isinstance(tab, RosterInfoTab) or\
- isinstance(tab, InfoTab):
+ if isinstance(tab, tabs.RosterInfoTab) or\
+ isinstance(tab, tabs.InfoTab):
return # The tab 0 should NEVER be closed
tab.on_close()
self.tabs.remove(tab)
@@ -1155,8 +1163,8 @@ class Core(object):
Opens the link in a browser, or join the room, or add the JID, or
copy it in the clipboard
"""
- if not isinstance(self.current_tab(), MucTab) and\
- not isinstance(self.current_tab(), PrivateTab):
+ if not isinstance(self.current_tab(), tabs.MucTab) and\
+ not isinstance(self.current_tab(), tabs.PrivateTab):
return
args = arg.split()
if len(args) > 2:
@@ -1229,7 +1237,7 @@ class Core(object):
else:
msg = None
for tab in self.tabs:
- if isinstance(tab, MucTab):
+ if isinstance(tab, tabs.MucTab):
muc.leave_groupchat(self.xmpp, tab.get_room().name, tab.get_room().own_nick, msg)
self.xmpp.disconnect()
self.running = False
@@ -1283,5 +1291,5 @@ class Core(object):
self.current_tab().just_before_refresh()
curses.doupdate()
-# # global core object
+# global core object
core = Core(connection)
diff --git a/src/tab.py b/src/tabs.py
index 4a76fca4..2dc6a017 100644
--- a/src/tab.py
+++ b/src/tabs.py
@@ -125,19 +125,19 @@ class Tab(object):
raise NotImplementedError
def on_input(self, key):
- raise NotImplementedError
+ pass
def on_lose_focus(self):
"""
called when this tab loses the focus.
"""
- raise NotImplementedError
+ pass
def on_gain_focus(self):
"""
called when this tab gains the focus.
"""
- raise NotImplementedError
+ pass
def add_message(self):
"""
@@ -146,25 +146,25 @@ class Tab(object):
FormTab, where text is not intented to be appened), it returns False.
If the tab can, it returns True
"""
- raise NotImplementedError
+ return False
def on_scroll_down(self):
"""
Defines what happens when we scrol down
"""
- raise NotImplementedError
+ pass
def on_scroll_up(self):
"""
Defines what happens when we scrol down
"""
- raise NotImplementedError
+ pass
def on_info_win_size_changed(self):
"""
Called when the window with the informations is resized
"""
- raise NotImplementedError
+ pass
def just_before_refresh(self):
"""
@@ -172,13 +172,13 @@ class Tab(object):
Particularly useful to move the cursor at the
correct position.
"""
- raise NotImplementedError
+ pass
def on_close(self):
"""
Called when the tab is to be closed
"""
- raise NotImplementedError
+ pass
class InfoTab(Tab):
"""
@@ -933,6 +933,102 @@ class ConversationTab(ChatTab):
def on_close(self):
return
+class MucListTab(Tab):
+ """
+ A tab listing rooms from a specific server, displaying various information,
+ scrollable, and letting the user join them, etc
+ """
+ def __init__(self, core, server):
+ Tab.__init__(self, core)
+ self._color_state = theme.COLOR_TAB_NORMAL
+ self.name = server
+ self.upper_message = windows.Topic()
+ columns = ('node-part','name', 'users')
+ self.list_header = windows.ColumnHeaderWin(columns)
+ self.listview = windows.ListWin(columns)
+ self.tab_win = windows.GlobalInfoBar()
+ self.default_help_message = windows.HelpText("“j”: join room. “i”: information")
+ self.input = self.default_help_message
+ self.key_func["KEY_DOWN"] = self.listview.move_cursor_down
+ self.key_func["KEY_UP"] = self.listview.move_cursor_up
+ self.key_func["/"] = self.on_slash
+ self.key_func['j'] = self.join_selected
+ self.key_func['J'] = self.join_selected_no_focus
+ self.resize()
+
+ def refresh(self, tabs, informations, roster):
+ self.upper_message.refresh('Chatroom list on server %s' % self.name)
+ self.list_header.refresh()
+ self.listview.refresh()
+ self.tab_win.refresh(tabs, tabs[0])
+ self.input.refresh()
+
+ def resize(self):
+ Tab.resize(self)
+ self.upper_message.resize(1, self.width, 0, 0, self.core.stdscr)
+ column_size = {'node-part': (self.width-5)//4,
+ 'name': (self.width-5)//4*3,
+ 'users': 5}
+ self.list_header.resize_columns(column_size)
+ self.list_header.resize(1, self.width, 1, 0, self.core.stdscr)
+ self.listview.resize_columns(column_size)
+ self.listview.resize(self.height-4, self.width, 2, 0, self.core.stdscr)
+ self.tab_win.resize(1, self.width, self.height-2, 0, self.core.stdscr)
+ self.input.resize(1, self.width, self.height-1, 0, self.core.stdscr)
+
+ def on_slash(self):
+ """
+ '/' is pressed, activate the input
+ """
+ curses.curs_set(1)
+ self.input = windows.CommandInput("", self.reset_help_message, self.execute_slash_command)
+ self.input.resize(1, self.width, self.height-1, 0, self.core.stdscr)
+ self.input.do_command("/") # we add the slash
+
+ def join_selected_no_focus(self):
+ return
+
+ def join_selected(self):
+ jid = self.listview.get_selected_row()['jid']
+ self.core.command_join(jid)
+
+ 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 get_color_state(self):
+ return theme.COLOR_TAB_NORMAL
+
+ def set_color_state(self, color):
+ pass
+
+ def get_name(self):
+ return self.name
+
+ def on_input(self, key):
+ res = self.input.do_command(key)
+ if res:
+ return True
+ if key in self.key_func:
+ return self.key_func[key]()
+
+ def on_lose_focus(self):
+ self._color_state = theme.COLOR_TAB_NORMAL
+
+ def on_gain_focus(self):
+ self._color_state = theme.COLOR_TAB_CURRENT
+ curses.curs_set(0)
+
+ def get_color_state(self):
+ return self._color_state
+
+
def diffmatch(search, string):
"""
Use difflib and a loop to check if search_pattern can
diff --git a/src/windows.py b/src/windows.py
index 0fa8505e..65796e5d 100644
--- a/src/windows.py
+++ b/src/windows.py
@@ -41,7 +41,7 @@ from contact import Contact, Resource
from roster import RosterGroup, roster
from message import Line
-from tab import MIN_WIDTH, MIN_HEIGHT
+from tabs import MIN_WIDTH, MIN_HEIGHT
from sleekxmpp.xmlstream.stanzabase import JID
@@ -55,17 +55,7 @@ class Win(object):
def _resize(self, height, width, y, x, parent_win):
self.height, self.width, self.x, self.y = height, width, x, y
- # try:
self._win = curses.newwin(height, width, y, x)
- # except:
- # # When resizing in a too little height (less than 3 lines)
- # # We don't need to resize the window, since this size
- # # just makes no sense
- # # Just don't crash when this happens.
- # # (°> also, a penguin
- # # //\
- # # V_/_
- # return
def _refresh(self):
self._win.noutrefresh()
@@ -404,10 +394,6 @@ class MucInfoWin(InfoWin):
self.addstr(txt, curses.color_pair(theme.COLOR_INFORMATION_BAR))
class TextWin(Win):
- """
- Just keep ONE single window for the text area and rewrite EVERYTHING
- on each change. (thanks weechat :o)
- """
def __init__(self):
Win.__init__(self)
@@ -1278,3 +1264,122 @@ class ContactInfoWin(Win):
elif isinstance(selected_row, Resource):
self.draw_contact_info(selected_row)
self._refresh()
+
+class ListWin(Win):
+ """
+ A list (with no depth, so not for the roster) that can be
+ scrolled up and down, with one selected line at a time
+ """
+ def __init__(self, columns, with_headers=True):
+ self._columns = columns # a tuple with the name of the columns
+ self._columns_sizes = {} # a dict {'column_name': size}
+ self.sorted_by = (None, None) # for example: ('name', '↑')
+ self.lines = [] # a list of dicts
+ self._selected_row = 0
+ self._starting_pos = 0 # The column number from which we start the refresh
+
+ def resize(self, height, width, y, x, stdscr):
+ self._resize(height, width, y, x, stdscr)
+
+ def resize_columns(self, dic):
+ """
+ Resize the width of the columns
+ """
+ self._columns_sizes = dic
+
+ def sort_by_column(self, col_name, asc=True):
+ """
+ Sort the list by the given column, ascendant or descendant
+ """
+ pass # TODO
+
+ def add_lines(self, lines):
+ """
+ Append some lines at the end of the list
+ """
+ if not lines:
+ return
+ self.lines += lines
+ self.refresh()
+
+ def get_selected_row(self):
+ """
+ Return the tuple representing the selected row
+ """
+ if self._selected_row:
+ return self.lines[self._selected_row]
+ return None
+
+ def refresh(self):
+ with g_lock:
+ self._win.erase()
+ lines = self.lines[self._starting_pos:self._starting_pos+self.height]
+ for y, line in enumerate(lines):
+ x = 0
+ for col in self._columns:
+ try:
+ txt = line[col] or ''
+ except (KeyError):
+ txt = ''
+ size = self._columns_sizes[col]
+ txt += ' ' * (size-len(txt))
+ if not txt:
+ continue
+ if line is self.lines[self._selected_row]:
+ self.addstr(y, x, txt[:size], curses.color_pair(theme.COLOR_INFORMATION_BAR))
+ else:
+ self.addstr(y, x, txt[:size])
+ x += size
+ self._refresh()
+
+ def move_cursor_down(self):
+ """
+ Move the cursor Down
+ """
+ if not self.lines:
+ return
+ if self._selected_row < len(self.lines) - 1:
+ self._selected_row += 1
+ while self._selected_row >= self._starting_pos + self.height:
+ self._starting_pos += self.height // 2
+ if self._starting_pos < 0:
+ self._starting_pos = 0
+ return True
+
+ def move_cursor_up(self):
+ """
+ Move the cursor Up
+ """
+ if not self.lines:
+ return
+ if self._selected_row > 0:
+ self._selected_row -= 1
+ while self._selected_row < self._starting_pos:
+ self._starting_pos -= self.height // 2
+ return True
+
+class ColumnHeaderWin(Win):
+ """
+ A class displaying the column's names
+ """
+ def __init__(self, columns):
+ self._columns = columns
+ self._columns_sizes = {}
+
+ def resize_columns(self, dic):
+ self._columns_sizes = dic
+
+ def resize(self, height, width, y, x, stdscr):
+ self._resize(height, width, y, x, stdscr)
+
+ def refresh(self):
+ with g_lock:
+ self._win.erase()
+ x = 0
+ for col in self._columns:
+ txt = col
+ size = self._columns_sizes[col]
+ txt += ' ' * (size-len(txt))
+ self.addstr(0, x, txt, curses.color_pair(theme.COLOR_STATUS_UNAVAILABLE))
+ x += size
+ self._refresh()