diff options
-rw-r--r-- | src/core.py | 25 | ||||
-rw-r--r-- | src/poezio.py | 66 | ||||
-rw-r--r-- | src/tabs.py | 78 | ||||
-rw-r--r-- | src/windows.py | 43 |
4 files changed, 114 insertions, 98 deletions
diff --git a/src/core.py b/src/core.py index e2edff8b..d2931933 100644 --- a/src/core.py +++ b/src/core.py @@ -26,6 +26,7 @@ import sys import shlex import curses import threading +import traceback from datetime import datetime @@ -79,20 +80,22 @@ class Core(object): User interface using ncurses """ def __init__(self, xmpp): + # All uncaught exception are given to this callback, instead + # of being displayed on the screen and exiting the program. + sys.excepthook = self.on_exception self.running = True self.stdscr = curses.initscr() self.init_curses(self.stdscr) self.xmpp = xmpp + # a unique buffer used to store global informations + # that are displayed in almost all tabs, in an + # information window. self.information_buffer = TextBuffer() self.information_win_size = config.get('info_win_height', 2, 'var') 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 - # that are displayed in almost all tabs, in an - # information window. - self.resize_timer = None self.previous_tab_nb = 0 self.own_nick = config.get('own_nick', self.xmpp.boundjid.bare) @@ -155,6 +158,20 @@ class Core(object): self.xmpp.add_event_handler("roster_update", self.on_roster_update) self.xmpp.add_event_handler("changed_status", self.on_presence) + def on_exception(self, typ, value, trace): + """ + When an exception in raised, open a special tab + displaying the traceback and some instructions to + make a bug report. + """ + try: + tb_tab = tabs.SimpleTextTab(self, "/!\ Oups, an error occured (this may not be fatal). /!\\\n\nPlease report this bug (by copying the present error message and explaining what you were doing) on the page http://codingteam.net/project/poezio/bugs/add\n\n%s\n\nIf Poezio does not respond anymore, kill it with Ctrl+\\, and sorry about that :(" % ''.join(traceback.format_exception(typ, value, trace))) + self.add_tab(tb_tab, focus=True) + except Exception: # If an exception is raised in this code, this is + # this is fatal, so we exit cleanly and display the traceback + curses.endwin() + raise + def grow_information_win(self): """ """ diff --git a/src/poezio.py b/src/poezio.py index 03b6d3e6..7989b4d5 100644 --- a/src/poezio.py +++ b/src/poezio.py @@ -20,70 +20,9 @@ Starting point of poezio. Launches both the Connection and Gui """ -import os -import curses import sys -import traceback -import threading - +import os sys.path.append(os.path.dirname(os.path.abspath(__file__))) - -def installThreadExcepthook(): - """ - Workaround for sys.excepthook thread bug - See http://bugs.python.org/issue1230540 - Python, you made me sad :( - """ - init_old = threading.Thread.__init__ - def init(self, *args, **kwargs): - init_old(self, *args, **kwargs) - run_old = self.run - def run_with_except_hook(*args, **kw): - try: - run_old(*args, **kw) - except (KeyboardInterrupt, SystemExit): - raise - except: - sys.excepthook(*sys.exc_info()) - self.run = run_with_except_hook - threading.Thread.__init__ = init - -class MyStdErr(object): - def __init__(self, fd): - """ - Change sys.stderr to something like /dev/null - to disable any printout on the screen that would - mess everything - """ - self.old_stderr = sys.stderr - sys.stderr = fd - def restore(self): - """ - Restore the good ol' sys.stderr, because we need - it in order to print the tracebacks - """ - sys.stderr.close() - sys.stderr = self.old_stderr - -# my_stderr = MyStdErr(open('/dev/null', 'a')) - -def exception_handler(type_, value, trace): - """ - on any traceback: exit ncurses and print the traceback - then exit the program - """ - my_stderr.restore() - try: - curses.endwin() - curses.echo() - except: # if an exception is raised but initscr has never been called yet - pass - traceback.print_exception(type_, value, trace, None, sys.stderr) - import os # used to quit the program even from a thread - os.abort() - -# sys.excepthook = exception_handler - import signal import logging @@ -96,4 +35,7 @@ if __name__ == '__main__': if options.debug: logging.basicConfig(filename=options.debug,level=logging.DEBUG) connection.start() # Connect to remote server + # Disable any display of non-wanted text on the terminal + # by redirecting stderr to /dev/null + # sys.stderr = open('/dev/null', 'a') core.main_loop() # Refresh the screen, wait for user events etc diff --git a/src/tabs.py b/src/tabs.py index c9707996..8016f86e 100644 --- a/src/tabs.py +++ b/src/tabs.py @@ -110,19 +110,19 @@ class Tab(object): """ returns the color that should be used in the GlobalInfoBar """ - raise NotImplementedError + return theme.COLOR_TAB_NORMAL def set_color_state(self, color): """ set the color state """ - raise NotImplementedError + pass def get_name(self): """ get the name of the tab """ - raise NotImplementedError + return self.__class__.__name__ def get_text_window(self): """ @@ -1039,14 +1039,51 @@ class MucListTab(Tab): self.core.execute(txt) return self.reset_help_message() + 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 theme.COLOR_TAB_NORMAL + return self._color_state - def set_color_state(self, color): - pass +class SimpleTextTab(Tab): + """ + A very simple tab, with just a text displaying some + information or whatever. + For example used to display tracebacks + """ + def __init__(self, core, text): + Tab.__init__(self, core) + self._color_state = theme.COLOR_TAB_NORMAL + self.text_win = windows.SimpleTextWin(text) + self.tab_win = windows.GlobalInfoBar() + self.default_help_message = windows.HelpText("“Ctrl+q”: close") + self.input = self.default_help_message + self.key_func['^T'] = self.close + self.key_func["/"] = self.on_slash + self.resize() - def get_name(self): - return self.name + 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 on_input(self, key): res = self.input.do_command(key) @@ -1055,6 +1092,19 @@ class MucListTab(Tab): if key in self.key_func: return self.key_func[key]() + def close(self): + self.core.close_tab() + + def resize(self): + self.text_win.resize(self.height-2, self.width, 0, 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 refresh(self, tabs, information, roster): + self.text_win.refresh() + self.tab_win.refresh(tabs, tabs[0]) + self.input.refresh() + def on_lose_focus(self): self._color_state = theme.COLOR_TAB_NORMAL @@ -1065,18 +1115,6 @@ class MucListTab(Tab): def get_color_state(self): return self._color_state -# class SimpleTextTab(Tab): -# """ -# A very simple tab, with just a text displaying some -# information or whatever -# """ -# def __init__(self, core, text): -# Tab.__init__(self, core) -# self.text = text -# self.text_win = - -# def resize(self): -# pass 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 a7cee054..7161b0dd 100644 --- a/src/windows.py +++ b/src/windows.py @@ -49,7 +49,7 @@ import theme g_lock = Lock() -LINES_NB_LIMIT = 16384 +LINES_NB_LIMIT = 4096 class Win(object): def __init__(self): @@ -1425,16 +1425,35 @@ class ColumnHeaderWin(Win): x += size self._refresh() -# class SimpleTextWin(Win): -# def __init__(self, text): -# self._text = text -# self.built_lines = [] +class SimpleTextWin(Win): + def __init__(self, text): + self._text = text + self.built_lines = [] -# def resize(self, height, width, y, x, stdscr): -# self._resize(height, width, y, x, stdscr) -# self.rebuild_text() + def resize(self, height, width, y, x, stdscr): + self._resize(height, width, y, x, stdscr) + self.rebuild_text() -# def rebuild_text(self): - -# def refresh(self): -# pass + def rebuild_text(self): + """ + Transform the text in lines than can then be + displayed without any calculation or anything + at refresh() time + It is basically called on each resize + """ + self.built_lines = [] + for line in self._text.split('\n'): + while len(line) >= self.width: + limit = line[:self.width].rfind(' ') + if limit <= 0: + limit = self.width + self.built_lines.append(line[:limit]) + line = line[limit:] + self.built_lines.append(line) + + def refresh(self): + with g_lock: + self._win.erase() + for y, line in enumerate(self.built_lines): + self.addstr(y, 0, line) + self._refresh() |