From 36620901e85a02fec31256b30eec55c754c80e85 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Sun, 4 May 2014 17:38:11 +0200 Subject: [PATCH] Create a more generic ListTab class, and make MucListTab inherite from it --- src/tabs/__init__.py | 1 + src/tabs/listtab.py | 203 +++++++++++++++++++++++++++++++++++++++++ src/tabs/muclisttab.py | 182 +++--------------------------------- 3 files changed, 215 insertions(+), 171 deletions(-) create mode 100644 src/tabs/listtab.py diff --git a/src/tabs/__init__.py b/src/tabs/__init__.py index 67c686b5..8826ede2 100644 --- a/src/tabs/__init__.py +++ b/src/tabs/__init__.py @@ -6,5 +6,6 @@ from . privatetab import PrivateTab from . conversationtab import ConversationTab, StaticConversationTab,\ DynamicConversationTab from . xmltab import XMLTab +from . listtab import ListTab from . muclisttab import MucListTab from . data_forms import DataFormsTab diff --git a/src/tabs/listtab.py b/src/tabs/listtab.py new file mode 100644 index 00000000..49588a0d --- /dev/null +++ b/src/tabs/listtab.py @@ -0,0 +1,203 @@ +""" +A generic tab that displays a serie of items in a scrollable, searchable, +sortable list. It should be inherited, to actually provide methods that +insert items in the list, and that lets the user interact with them. +""" + +from gettext import gettext as _ + +import logging +log = logging.getLogger(__name__) + +import curses +import collections + +import windows +from common import safeJID +from decorators import refresh_wrapper + +from . import Tab + + +class ListTab(Tab): + plugin_commands = {} + plugin_keys = {} + + def __init__(self, name, help_message, header_text, cols): + """Parameters: + name: The name of the tab + help_message: The default help message displayed instead of the + input + header_text: The text displayed on the header line, at the top of + the tab + cols: a tuple of 2-tuples. e.g. (('column1_name', number), + ('column2_name', number)) + """ + Tab.__init__(self) + self.state = 'normal' + self.name = name + columns = collections.OrderedDict() + for col, num in cols: + columns[col] = num + self.list_header = windows.ColumnHeaderWin(list(columns)) + self.listview = windows.ListWin(columns) + self.info_header = windows.MucListInfoWin(header_text) + self.default_help_message = windows.HelpText(help_message) + self.input = self.default_help_message + self.key_func["KEY_DOWN"] = self.move_cursor_down + self.key_func["KEY_UP"] = self.move_cursor_up + self.key_func['^I'] = self.completion + self.key_func["/"] = self.on_slash + self.key_func['KEY_LEFT'] = self.list_header.sel_column_left + self.key_func['KEY_RIGHT'] = self.list_header.sel_column_right + self.key_func[' '] = self.sort_by + self.register_command('close', self.close, + shortdesc=_('Close this tab.')) + self.resize() + self.update_keys() + self.update_commands() + + def get_columns_sizes(self): + """ + Must be implemented in subclasses. Must return a dict like this: + {'column1_name': size1, + 'column2_name': size2} + Where the size are calculated based on the size of the tab etc + """ + raise NotImplementedError + + + def refresh(self): + if self.need_resize: + self.resize() + log.debug(' TAB Refresh: %s', self.__class__.__name__) + if self.size.tab_degrade_y: + display_info_win = False + else: + display_info_win = True + + self.info_header.refresh(window=self.listview) + if display_info_win: + self.info_win.refresh() + self.refresh_tab_win() + self.list_header.refresh() + self.listview.refresh() + self.input.refresh() + + def resize(self): + if self.size.tab_degrade_y: + info_win_height = 0 + tab_win_height = 0 + else: + info_win_height = self.core.information_win_size + tab_win_height = Tab.tab_win_height() + + self.info_header.resize(1, self.width, + self.height - 2 - info_win_height + - tab_win_height, + 0) + column_size = self.get_columns_sizes() + self.list_header.resize_columns(column_size) + self.list_header.resize(1, self.width, 0, 0) + self.listview.resize_columns(column_size) + self.listview.resize(self.height - 3 - info_win_height - tab_win_height, + self.width, 1, 0) + self.input.resize(1, self.width, self.height-1, 0) + self.push_size() + + 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.input.do_command("/") # we add the slash + + def close(self, arg=None): + self.input.on_delete() + self.core.close_tab(self) + + def set_error(self, msg, code, body): + """ + If there's an error (retrieving the values etc) + """ + self._error_message = _('Error: %(code)s - %(msg)s: %(body)s') % {'msg':msg, 'body':body, 'code':code} + self.info_header.message = self._error_message + self.info_header.refresh() + curses.doupdate() + + def sort_by(self): + if self.list_header.get_order(): + self.listview.sort_by_column( + col_name=self.list_header.get_sel_column(), + asc=False) + self.list_header.set_order(False) + self.list_header.refresh() + else: + self.listview.sort_by_column( + col_name=self.list_header.get_sel_column(), + asc=True) + self.list_header.set_order(True) + self.list_header.refresh() + self.core.doupdate() + + @refresh_wrapper.always + def reset_help_message(self, _=None): + curses.curs_set(0) + self.input = self.default_help_message + self.input.resize(1, self.width, self.height-1, 0) + return True + + def execute_slash_command(self, txt): + if txt.startswith('/'): + self.input.key_enter() + self.execute_command(txt) + return self.reset_help_message() + + def completion(self): + if isinstance(self.input, windows.Input): + self.complete_commands(self.input) + + def on_input(self, key, raw): + res = self.input.do_command(key, raw=raw) + if res and not isinstance(self.input, windows.Input): + return True + elif res: + return False + if not raw and key in self.key_func: + return self.key_func[key]() + + def on_info_win_size_changed(self): + if self.core.information_win_size >= self.height-3: + return + self.info_header.resize(1, self.width, self.height-2-self.core.information_win_size - Tab.tab_win_height(), 0) + self.listview.resize(self.height-3-self.core.information_win_size - Tab.tab_win_height(), self.width, 1, 0) + + def on_lose_focus(self): + self.state = 'normal' + + def on_gain_focus(self): + self.state = 'current' + curses.curs_set(0) + + def on_scroll_up(self): + return self.listview.scroll_up() + + def on_scroll_down(self): + return self.listview.scroll_down() + + def move_cursor_up(self): + self.listview.move_cursor_up() + self.listview.refresh() + self.core.doupdate() + + def move_cursor_down(self): + self.listview.move_cursor_down() + self.listview.refresh() + self.core.doupdate() + + def matching_names(self): + return [(2, self.name)] + + diff --git a/src/tabs/muclisttab.py b/src/tabs/muclisttab.py index 3d43928a..d7c68588 100644 --- a/src/tabs/muclisttab.py +++ b/src/tabs/muclisttab.py @@ -9,20 +9,11 @@ from gettext import gettext as _ import logging log = logging.getLogger(__name__) -import curses -import collections -from datetime import datetime +from . import ListTab from sleekxmpp.plugins.xep_0030.stanza.items import DiscoItem -import windows -from common import safeJID -from decorators import refresh_wrapper - -from . import Tab - - -class MucListTab(Tab): +class MucListTab(ListTab): """ A tab listing rooms from a specific server, displaying various information, scrollable, and letting the user join them, etc @@ -31,100 +22,23 @@ class MucListTab(Tab): plugin_keys = {} def __init__(self, server): - Tab.__init__(self) - self.state = 'normal' - self.name = server - columns = collections.OrderedDict() - columns['node-part'] = 0 - columns['name'] = 2 - columns['users'] = 3 - self.list_header = windows.ColumnHeaderWin(list(columns)) - self.listview = windows.ListWin(columns) - self.info_header = windows.MucListInfoWin(_('Chatroom list on server %s (Loading)') % self.name) - self.default_help_message = windows.HelpText("ā€œjā€: join room.") - self.input = self.default_help_message - self.key_func["KEY_DOWN"] = self.move_cursor_down - self.key_func["KEY_UP"] = self.move_cursor_up - self.key_func['^I'] = self.completion - self.key_func["/"] = self.on_slash + ListTab.__init__(self, server, + "ā€œjā€: join room.", + _('Chatroom list on server %s (Loading)') % server, + (('node-part', 0), ('name', 2), ('users', 3))) self.key_func['j'] = self.join_selected self.key_func['J'] = self.join_selected_no_focus self.key_func['^M'] = self.join_selected - self.key_func['KEY_LEFT'] = self.list_header.sel_column_left - self.key_func['KEY_RIGHT'] = self.list_header.sel_column_right - self.key_func[' '] = self.sort_by - self.register_command('close', self.close, - shortdesc=_('Close this tab.')) - self.resize() - self.update_keys() - self.update_commands() - def refresh(self): - if self.need_resize: - self.resize() - log.debug(' TAB Refresh: %s', self.__class__.__name__) - if self.size.tab_degrade_y: - display_info_win = False - else: - display_info_win = True - - self.info_header.refresh(window=self.listview) - if display_info_win: - self.info_win.refresh() - self.refresh_tab_win() - self.list_header.refresh() - self.listview.refresh() - self.input.refresh() - - def resize(self): - if self.size.tab_degrade_y: - info_win_height = 0 - tab_win_height = 0 - else: - info_win_height = self.core.information_win_size - tab_win_height = Tab.tab_win_height() - - self.info_header.resize(1, self.width, - self.height - 2 - info_win_height - - tab_win_height, - 0) - column_size = {'node-part': int(self.width* 2 / 8), - 'name': int(self.width * 5 / 8), - 'users': self.width - int(self.width * 2 / 8) - - int(self.width * 5 / 8)} - self.list_header.resize_columns(column_size) - self.list_header.resize(1, self.width, 0, 0) - self.listview.resize_columns(column_size) - self.listview.resize(self.height - 3 - info_win_height - tab_win_height, - self.width, 1, 0) - self.input.resize(1, self.width, self.height-1, 0) - self.push_size() - - 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.input.do_command("/") # we add the slash - - def close(self, arg=None): - self.input.on_delete() - self.core.close_tab(self) + def get_columns_sizes(self): + return {'node-part': int(self.width* 2 / 8), + 'name': int(self.width * 5 / 8), + 'users': self.width - int(self.width * 2 / 8) + - int(self.width * 5 / 8)} def join_selected_no_focus(self): return - def set_error(self, msg, code, body): - """ - If there's an error (retrieving the values etc) - """ - self._error_message = _('Error: %(code)s - %(msg)s: %(body)s') % {'msg':msg, 'body':body, 'code':code} - self.info_header.message = self._error_message - self.info_header.refresh() - curses.doupdate() - def on_muc_list_item_received(self, iq): """ Callback called when a disco#items result is received @@ -150,83 +64,9 @@ class MucListTab(Tab): self.refresh_tab_win() self.core.doupdate() - def sort_by(self): - if self.list_header.get_order(): - self.listview.sort_by_column( - col_name=self.list_header.get_sel_column(), - asc=False) - self.list_header.set_order(False) - self.list_header.refresh() - else: - self.listview.sort_by_column( - col_name=self.list_header.get_sel_column(), - asc=True) - self.list_header.set_order(True) - self.list_header.refresh() - self.core.doupdate() - def join_selected(self): row = self.listview.get_selected_row() if not row: return self.core.command_join(row[1]) - @refresh_wrapper.always - def reset_help_message(self, _=None): - curses.curs_set(0) - self.input = self.default_help_message - self.input.resize(1, self.width, self.height-1, 0) - return True - - def execute_slash_command(self, txt): - if txt.startswith('/'): - self.input.key_enter() - self.execute_command(txt) - return self.reset_help_message() - - def completion(self): - if isinstance(self.input, windows.Input): - self.complete_commands(self.input) - - def on_input(self, key, raw): - res = self.input.do_command(key, raw=raw) - if res and not isinstance(self.input, windows.Input): - return True - elif res: - return False - if not raw and key in self.key_func: - return self.key_func[key]() - - def on_info_win_size_changed(self): - if self.core.information_win_size >= self.height-3: - return - self.info_header.resize(1, self.width, self.height-2-self.core.information_win_size - Tab.tab_win_height(), 0) - self.listview.resize(self.height-3-self.core.information_win_size - Tab.tab_win_height(), self.width, 1, 0) - - def on_lose_focus(self): - self.state = 'normal' - - def on_gain_focus(self): - self.state = 'current' - curses.curs_set(0) - - def on_scroll_up(self): - return self.listview.scroll_up() - - def on_scroll_down(self): - return self.listview.scroll_down() - - def move_cursor_up(self): - self.listview.move_cursor_up() - self.listview.refresh() - self.core.doupdate() - - def move_cursor_down(self): - self.listview.move_cursor_down() - self.listview.refresh() - self.core.doupdate() - - def matching_names(self): - return [(2, self.name)] - -