diff --git a/gramps2/ChangeLog b/gramps2/ChangeLog index 3ff5dcb49..55d5f4cf6 100644 --- a/gramps2/ChangeLog +++ b/gramps2/ChangeLog @@ -1,3 +1,10 @@ +2006-01-05 Richard Taylor + * src/Assistant.py: added callback support for pages + * src/GrampsLogger/_ErrorReportAssistant.py src/GrampsLogger/_ErrorView.py + src/GrampsLogger/_GtkHandler.py src/GrampsLogger/_RotateHandler.py + test/GrampsLogger/GtkHandler_Test.py test/GrampsLogger/RotateLogger_Test.py: + added initial logging and error reporting framework. + 2006-01-04 Don Allingham * src/FamilyView.py: Incorporate Steve Hall's identing ideas, general classes * src/DisplayView.py: Open/OpenRecent MenuToolButton goodness :-) diff --git a/gramps2/src/Assistant.py b/gramps2/src/Assistant.py index 33c4973dc..bce7c55fb 100644 --- a/gramps2/src/Assistant.py +++ b/gramps2/src/Assistant.py @@ -40,6 +40,7 @@ class Assistant: self.current_page = 0 self.max_page = 1 + self.page_callbacks = {} self.window = gtk.Window() titlebox = gtk.HBox() @@ -100,6 +101,8 @@ class Assistant: self.next.set_use_stock(True) self.cancel.show() + self.call_page_callback() + def next_clicked(self,obj): if self.current_page == self.max_page: self.complete() @@ -120,6 +123,15 @@ class Assistant: self.back.set_sensitive(True) self.cancel.show() + self.call_page_callback() + + def call_page_callback(self): + # If the page has a callback then call it. + if self.page_callbacks.has_key( + self.notebook.get_nth_page(self.notebook.get_current_page())): + self.page_callbacks[ + self.notebook.get_nth_page(self.notebook.get_current_page())]() + def set_intro(self,text): hbox = gtk.HBox(spacing=12) image = gtk.Image() @@ -133,10 +145,12 @@ class Assistant: def set_conclusion(self,title,text): self.conclude_text = text - def add_page(self, title, child): + def add_page(self, title, child, callback=None): self.title_text.append(_format % title) self.notebook.append_page(child) self.max_page += 1 + if callback is not None: + self.page_callbacks[child] = callback def show(self): self.title_text.append(_format % _('Finished')) @@ -151,6 +165,9 @@ class Assistant: self.window.show_all() self.notebook.set_current_page(0) + self.call_page_callback() + + def destroy(self): self.window.destroy() diff --git a/gramps2/src/GrampsLogger/_ErrorReportAssistant.py b/gramps2/src/GrampsLogger/_ErrorReportAssistant.py new file mode 100644 index 000000000..beb749e28 --- /dev/null +++ b/gramps2/src/GrampsLogger/_ErrorReportAssistant.py @@ -0,0 +1,205 @@ +from gettext import gettext as _ + +import sys,os +import const + +import gtk +import Assistant + +class ErrorReportAssistant: + + def __init__(self,error_detail,rotate_handler): + self._error_detail = error_detail + self._rotate_handler = rotate_handler + + self._sys_information_text_buffer = None + self._user_information_text_buffer = None + self._error_details_text_buffer = None + self._final_report_text_buffer = None + + self.w = Assistant.Assistant(_('Report a bug'),self.complete) + + self.w.set_intro(_("This is the Bug Reporting Assistant. It will "\ + "help you to make a bug report to the Gramps "\ + "developers that will be as detailed as possible.\n"\ + "The assistant will ask you a few questions and will "\ + "gather some information about the error that has "\ + "occured and the operating environment. "\ + "At then end of the assistent you will be asked to "\ + "send an email to the Gramps bug reporting mailing list "\ + "and the bug report will be placed on the clip board so "\ + "that you can paste it into your email programme and review "\ + "exactly what information is being sent.")) + + + self.w.add_page(_("Error Details"), self.build_page2()) + self.w.add_page(_("System Information"), self.build_page3()) + self.w.add_page(_("Further Information"), self.build_page4()) + self.w.add_page(_("Summary"), self.build_page5(),self.page5_update) + + self.w.set_conclusion(_('Complete'), + _('The error report will be copied to your clipboard when you click OK. \n' + 'Please paste the report into your favourite email client and send it to: \n\n' + 'gramps-bugs@lists.sourceforge.net\n\n' + 'GRAMPS is an Open Source project. Its success ' + 'depends on the users. User feedback is important. ' + 'Thankyou for taking the time to submit a bug report.')) + + self.w.show() + + def complete(self): + clipboard = gtk.Clipboard() + clipboard.set_text( + self._final_report_text_buffer.get_text( + self._final_report_text_buffer.get_start_iter(), + self._final_report_text_buffer.get_end_iter())) + + clipboard = gtk.Clipboard(selection="PRIMARY") + clipboard.set_text( + self._final_report_text_buffer.get_text( + self._final_report_text_buffer.get_start_iter(), + self._final_report_text_buffer.get_end_iter())) + + def _get_sys_information(self): + return "Python version: %s \n"\ + "Gramps version: %s \n"\ + "OS: %s\n"\ + "Distribution: %s\n"\ + % (str(sys.version).replace('\n',''), + str(const.version), + os.uname()[0], + os.uname()[2]) + + def build_page2(self): + + box = gtk.VBox() + + label = gtk.Label(_("This is the detail Gramps error information, don't worry if you "\ + "do not understand it. If you can see that there is any personal "\ + "informatin included in the error details please remove it, you "\ + "will have the opportunity to add further detail about the error "\ + "in the following pages of the assistant.")) + + label.set_line_wrap(True) + + box.pack_start(label,False,False,5) + + sw = gtk.ScrolledWindow() + sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + textview = gtk.TextView() + + self._error_details_text_buffer = textview.get_buffer() + self._error_details_text_buffer.set_text(self._error_detail) + + sw.add(textview) + sw.show() + textview.show() + + box.pack_start(sw) + box.show_all() + + return box + + def build_page3(self): + + box = gtk.VBox() + + label = gtk.Label(_("Please check the information below and correct anything that "\ + "you know to be wrong or remove anything that you would rather not "\ + "have included in the bug report.")) + + label.set_line_wrap(True) + + box.pack_start(label,False,False,5) + + sw = gtk.ScrolledWindow() + sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + textview = gtk.TextView() + + self._sys_information_text_buffer = textview.get_buffer() + self._sys_information_text_buffer.set_text(self._get_sys_information()) + + sw.add(textview) + sw.show() + textview.show() + + box.pack_start(sw) + box.show_all() + + return box + + def build_page4(self): + + box = gtk.VBox() + + label = gtk.Label(_("Please provide as much information as you can "\ + "about what you were doing when the error occured. ")) + + label.set_line_wrap(True) + + box.pack_start(label,False,False,5) + + sw = gtk.ScrolledWindow() + sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + textview = gtk.TextView() + + self._user_information_text_buffer = textview.get_buffer() + + sw.add(textview) + sw.show() + textview.show() + + box.pack_start(sw) + box.show_all() + + return box + + + def build_page5(self): + + box = gtk.VBox() + + label = gtk.Label(_("The complete bug report is shown below. When you click Forward it will "\ + "be copied onto the clickboard and you will be asked to email it.\n"\ + "Please check that the information is correct, do not worry if you "\ + "don't understand the detail of the error information. Just make sure "\ + "that it does not contain anything that you do not want to be sent "\ + "to the developers.")) + + label.set_line_wrap(True) + + box.pack_start(label,False,False,5) + + sw = gtk.ScrolledWindow() + sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + textview = gtk.TextView() + textview.set_editable(False) + textview.set_cursor_visible(False) + + self._final_report_text_buffer = textview.get_buffer() + + sw.add(textview) + sw.show() + textview.show() + + box.pack_start(sw) + box.show_all() + + return box + + def page5_update(self): + + self._final_report_text_buffer.set_text( + "System Information: \n\n" + + self._sys_information_text_buffer.get_text( + self._sys_information_text_buffer.get_start_iter(), + self._sys_information_text_buffer.get_end_iter()) + + "Additional Information: \n\n" + + self._user_information_text_buffer.get_text( + self._user_information_text_buffer.get_start_iter(), + self._user_information_text_buffer.get_end_iter()) + + "\nError Details: \n\n" + + self._error_details_text_buffer.get_text( + self._error_details_text_buffer.get_start_iter(), + self._error_details_text_buffer.get_end_iter()) + ) diff --git a/gramps2/src/GrampsLogger/_ErrorView.py b/gramps2/src/GrampsLogger/_ErrorView.py new file mode 100644 index 000000000..b6c860edb --- /dev/null +++ b/gramps2/src/GrampsLogger/_ErrorView.py @@ -0,0 +1,74 @@ +from gettext import gettext as _ + +import gtk + +from _ErrorReportAssistant import ErrorReportAssistant + +class ErrorView(object): + """ + A Dialog for displaying errors. + """ + + def __init__(self, error_detail, rotate_handler): + """ + Initialize the handler with the buffer size. + """ + + self._error_detail = error_detail + self._rotate_handler = rotate_handler + + self.draw_window() + self.run() + + def run(self): + self.response = self.top.run() + if self.response == gtk.RESPONSE_HELP: + self.help_clicked() + elif self.response == gtk.RESPONSE_YES: + ErrorReportAssistant(error_detail = self._error_detail, + rotate_handler = self._rotate_handler) + self.top.destroy() + + def draw_window(self): + title = "%s - GRAMPS" % _("Error Report") + self.top = gtk.Dialog(title) + #self.top.set_default_size(400,350) + self.top.set_has_separator(False) + self.top.vbox.set_spacing(5) + label = gtk.Label('%s' + % _("An unexpected error has occured")) + label.set_use_markup(True) + self.top.vbox.pack_start(label,False,False,5) + + sep = gtk.HSeparator() + self.top.vbox.pack_start(sep,False,False,5) + + instructions_label = gtk.Label('%s\n'\ + '%s' % \ + ( _("Gramps has experienced an unexpected error."), + _("Your data will safe but it would be advisable to restart gramps immediately. "\ + "If you would like to report the problem to the Gramps team "\ + "please click Report and the Error Reporting Wizard will help you "\ + "to make a bug report."))) + instructions_label.set_line_wrap(True) + instructions_label.set_use_markup(True) + + self.top.vbox.pack_start(instructions_label,False,False,5) + + tb_frame = gtk.Frame(_("Error Detail")) + tb_label = gtk.Label(self._error_detail) + + tb_frame.add(tb_label) + + tb_expander = gtk.Expander('%s' % _("Error Detail")) + tb_expander.set_use_markup(True) + tb_expander.add(tb_frame) + + self.top.vbox.pack_start(tb_expander,True,True,5) + + + self.top.add_button(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL) + self.top.add_button(_("Report"),gtk.RESPONSE_YES) + self.top.add_button(gtk.STOCK_HELP,gtk.RESPONSE_HELP) + + self.top.show_all() diff --git a/gramps2/src/GrampsLogger/_GtkHandler.py b/gramps2/src/GrampsLogger/_GtkHandler.py new file mode 100644 index 000000000..2f668c245 --- /dev/null +++ b/gramps2/src/GrampsLogger/_GtkHandler.py @@ -0,0 +1,27 @@ + +import logging + +from _ErrorView import ErrorView + + +class GtkHandler(logging.Handler): + """ + A handler class which pops up a Gtk Window when a log message occurs. + """ + + def __init__(self,rotate_handler=None): + """ + Initialize the handler with a optional rotate_logger instance. + """ + logging.Handler.__init__(self) + self._rotate_handler = rotate_handler + + + + def emit(self, record): + """ + Add the record to the rotating buffer. + + """ + + ErrorView(error_detail=self.format(record),rotate_handler=self._rotate_handler) diff --git a/gramps2/src/GrampsLogger/_RotateHandler.py b/gramps2/src/GrampsLogger/_RotateHandler.py new file mode 100644 index 000000000..3d36b7104 --- /dev/null +++ b/gramps2/src/GrampsLogger/_RotateHandler.py @@ -0,0 +1,40 @@ +import logging + +class RotateHandler(logging.Handler): + """ + A handler class which buffers logging records in memory. A rotating + buffer is used so that the last X records are always available. + """ + def __init__(self, capacity): + """ + Initialize the handler with the buffer size. + """ + logging.Handler.__init__(self) + + self.set_capacity(capacity) + + + def emit(self, record): + """ + Add the record to the rotating buffer. + + """ + self._buffer[self._index] = record + self._index = (self._index + 1 ) % self._capacity + + + def get_buffer(self): + """ + Return the buffer with the records in the correct order. + """ + + return [record for record in self._buffer[self._index:] + self._buffer[:self._index] + if record is not None] + + def set_capacity(self,capacity): + """ + Set the number of log records that will be stored. + """ + self._capacity = capacity + self._index = 0 + self._buffer = self._capacity * [None] diff --git a/gramps2/test/GrampsLogger/GtkHandler_Test.py b/gramps2/test/GrampsLogger/GtkHandler_Test.py new file mode 100644 index 000000000..6f6d6232e --- /dev/null +++ b/gramps2/test/GrampsLogger/GtkHandler_Test.py @@ -0,0 +1,48 @@ +import unittest +import logging +import sys +import gtk + + +sys.path.append('../../src') +sys.path.append('../../src/GrampsLogger') + +logger = logging.getLogger('Gramps.Tests.GrampsLogger') + +import const +const.rootDir = "../../src" + +import _GtkHandler + + +class GtkHandlerTest(unittest.TestCase): + """Test the GtkHandler.""" + + def test_window(self): + """Test that the window appears.""" + + rh = _GtkHandler.GtkHandler() + l = logging.getLogger("GtkHandlerTest") + l.setLevel(logging.ERROR) + + l.addHandler(rh) + + log_message = "Debug message" + try: + wibble + except: + l.error(log_message,exc_info=True) + + l.removeHandler(rh) + + gtk.main() + + + +def testSuite(): + suite = unittest.makeSuite(GtkHandlerTest,'test') + return suite + + +if __name__ == '__main__': + unittest.TextTestRunner().run(testSuite()) diff --git a/gramps2/test/GrampsLogger/RotateLogger_Test.py b/gramps2/test/GrampsLogger/RotateLogger_Test.py new file mode 100644 index 000000000..2b10c077a --- /dev/null +++ b/gramps2/test/GrampsLogger/RotateLogger_Test.py @@ -0,0 +1,87 @@ +import unittest +import logging +import sys + +sys.path.append('../../src/GrampsLogger') + +logger = logging.getLogger('Gramps.Tests.GrampsLogger') + +import _RotateHandler + +class RotateHandlerTest(unittest.TestCase): + """Test the RotateHandler.""" + + def test_buffer_recall(self): + """Test that simple recall of messages works.""" + + rh = _RotateHandler.RotateHandler(10) + l = logging.getLogger("RotateHandlerTest") + l.setLevel(logging.DEBUG) + + l.addHandler(rh) + + log_message = "Debug message" + l.info(log_message) + assert len(rh.get_buffer()) == 1, "Message buffer wrong size, should be '1' is '%d'" % (len(rh.get_buffer())) + assert rh.get_buffer()[0].getMessage() == log_message, \ + "Message buffer content is wrong, should be '%s' is '%s'" \ + % (log_message, rh.get_buffer()[0].getMessage()) + + l.removeHandler(rh) + + + def test_buffer_rotation(self): + """Test that buffer correctly rolls over when capacity is reached.""" + + rh = _RotateHandler.RotateHandler(10) + l = logging.getLogger("RotateHandlerTest") + l.setLevel(logging.DEBUG) + + l.addHandler(rh) + + log_messages = 20 * [None] + for i in xrange(0,20): + log_messages[i] = "Message %d" % (i) + + + [l.info(log_messages[i]) for i in xrange(0,10)] + + assert len(rh.get_buffer()) == 10, "Message buffer wrong size, should be '10' is '%d'" % (len(rh.get_buffer())) + + buffer = rh.get_buffer() + + for i in xrange(0,10): + assert buffer[i].getMessage() == log_messages[i], \ + "Message buffer content is wrong, should be '%s' is '%s'. i = '%d'" \ + % (log_messages[i], buffer[i].getMessage(),i) + + + l.info(log_messages[10]) + + buffer = rh.get_buffer() + + for i in xrange(0,10): + assert buffer[i].getMessage() == log_messages[i+1], \ + "Message buffer content is wrong, should be '%s' is '%s'. i = '%d'" \ + % (log_messages[i+1], buffer[i].getMessage(),i) + + + [l.info(log_messages[i]) for i in xrange(11,20)] + + buffer = rh.get_buffer() + for i in xrange(0,10): + assert buffer[i].getMessage() == log_messages[i+10], \ + "Message buffer content is wrong, should be '%s' is '%s'. i = '%d'" \ + % (log_messages[i+10], buffer[i].getMessage(),i) + + l.removeHandler(rh) + + + +def testSuite(): + suite = unittest.makeSuite(RotateHandlerTest,'test') + return suite + + +if __name__ == '__main__': + unittest.TextTestRunner().run(testSuite())