1# 2# Gramps - a GTK+/GNOME based genealogy program 3# 4# Copyright (C) 2001-2006 Donald N. Allingham 5# Copyright (C) 2008 Brian G. Matherly 6# Copyright (C) 2010 Jakim Friant 7# Copyright (C) 2011-2012 Paul Franklin 8# 9# This program is free software; you can redistribute it and/or modify 10# it under the terms of the GNU General Public License as published by 11# the Free Software Foundation; either version 2 of the License, or 12# (at your option) any later version. 13# 14# This program is distributed in the hope that it will be useful, 15# but WITHOUT ANY WARRANTY; without even the implied warranty of 16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17# GNU General Public License for more details. 18# 19# You should have received a copy of the GNU General Public License 20# along with this program; if not, write to the Free Software 21# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 22# 23 24""" The ReportDialog base class """ 25 26#------------------------------------------------------------------------- 27# 28# Python modules 29# 30#------------------------------------------------------------------------- 31import os 32 33import logging 34LOG = logging.getLogger(".") 35 36#------------------------------------------------------------------------- 37# 38# GTK+ modules 39# 40#------------------------------------------------------------------------- 41from gi.repository import Gtk 42 43#------------------------------------------------------------------------- 44# 45# Gramps modules 46# 47#------------------------------------------------------------------------- 48from gramps.gen.config import config 49from gramps.gen.const import URL_MANUAL_PAGE, DOCGEN_OPTIONS 50from gramps.gen.errors import (DatabaseError, FilterError, ReportError, 51 WindowActiveError) 52from ...utils import open_file_with_default_application 53from .. import add_gui_options, make_gui_option 54from ...user import User 55from ...dialog import ErrorDialog, OptionDialog 56from gramps.gen.plug.report import (CATEGORY_TEXT, CATEGORY_DRAW, CATEGORY_BOOK, 57 CATEGORY_CODE, CATEGORY_WEB, 58 CATEGORY_GRAPHVIZ, CATEGORY_TREE, 59 standalone_categories) 60from gramps.gen.plug.docgen import StyleSheet, StyleSheetList 61from ...managedwindow import ManagedWindow 62from ._stylecombobox import StyleComboBox 63from ._styleeditor import StyleListDisplay 64from ._fileentry import FileEntry 65from gramps.gen.const import GRAMPS_LOCALE as glocale 66_ = glocale.translation.gettext 67#------------------------------------------------------------------------- 68# 69# Private Constants 70# 71#------------------------------------------------------------------------- 72URL_REPORT_PAGE = URL_MANUAL_PAGE + "_-_Reports" 73 74#------------------------------------------------------------------------- 75# 76# ReportDialog class 77# 78#------------------------------------------------------------------------- 79class ReportDialog(ManagedWindow): 80 """ 81 The ReportDialog base class. This is a base class for generating 82 customized dialogs to solicit options for a report. It cannot be 83 used as is, but it can be easily sub-classed to create a functional 84 dialog for a stand-alone report. 85 """ 86 border_pad = 6 87 88 def __init__(self, dbstate, uistate, option_class, name, trans_name, 89 track=[]): 90 """Initialize a dialog to request that the user select options 91 for a basic *stand-alone* report.""" 92 93 self.style_name = "default" 94 self.firstpage_added = False 95 self.raw_name = name 96 self.dbstate = dbstate 97 self.uistate = uistate 98 self.db = dbstate.db 99 self.report_name = trans_name 100 101 ManagedWindow.__init__(self, uistate, track, self) 102 103 self.init_options(option_class) 104 self.init_interface() 105 106 def close(self, *obj): 107 """ 108 Close itself. 109 cleanup things that can prevent garbage collection 110 """ 111 if hasattr(self, 'widgets'): # handle pathlogical bug 4145 112 totwidg = list(range(len(self.widgets))) 113 totwidg.reverse() 114 for ind in totwidg: 115 if hasattr(self.widgets[ind][1], 'clean_up'): 116 self.widgets[ind][1].clean_up() 117 del self.widgets[ind] 118 delattr(self, 'widgets') 119 for name, fram in self.frames.items(): 120 totwidg = list(range(len(fram))) 121 totwidg.reverse() 122 for ind in totwidg: 123 if hasattr(fram[ind][1], 'clean_up'): 124 fram[ind][1].clean_up() 125 del fram[ind] 126 self.frames.clear() 127 self.frames = None 128 ManagedWindow.close(self, *obj) 129 130 def init_options(self, option_class): 131 try: 132 if issubclass(option_class, object): 133 self.options = option_class(self.raw_name, self.db) 134 except TypeError: 135 self.options = option_class 136 self.options.load_previous_values() 137 138 def build_window_key(self, obj): 139 key = self.raw_name 140 return key 141 142 def build_menu_names(self, obj): 143 return (_("Configuration"), self.report_name) 144 145 def init_interface(self): 146 self.widgets = [] 147 self.doc_widgets = [] 148 self.frame_names = [] 149 self.frames = {} 150 self.format_menu = None 151 self.style_button = None 152 153 self.style_name = self.options.handler.get_default_stylesheet_name() 154 155 window = Gtk.Dialog(title='Gramps') 156 self.set_window(window, None, self.get_title()) 157 self.window.set_modal(True) 158 159 self.help = self.window.add_button(_('_Help'), Gtk.ResponseType.HELP) 160 self.help.connect('clicked', self.on_help_clicked) 161 162 self.cancel = self.window.add_button(_('_Cancel'), 163 Gtk.ResponseType.CANCEL) 164 self.cancel.connect('clicked', self.on_cancel) 165 166 self.ok = self.window.add_button(_('_OK'), Gtk.ResponseType.OK) 167 self.ok.connect('clicked', self.on_ok_clicked) 168 169 self.window.set_position(Gtk.WindowPosition.CENTER_ON_PARENT) 170 self.window.set_default_size(600, -1) 171 172 # Set up and run the dialog. These calls are not in top down 173 # order when looking at the dialog box as there is some 174 # interaction between the various frames. 175 176 self.setup_title() 177 self.setup_header() 178 self.grid = Gtk.Grid() 179 self.grid.set_column_spacing(12) 180 self.grid.set_row_spacing(6) 181 self.grid.set_border_width(6) 182 self.row = 0 183 184 # Build the list of widgets that are used to extend the Options 185 # frame and to create other frames 186 self.add_user_options() 187 188 self.setup_init() 189 self.setup_format_frame() 190 self.setup_target_frame() 191 self.setup_style_frame() 192 193 self.notebook = Gtk.Notebook() 194 self.notebook.set_scrollable(True) 195 self.notebook.set_border_width(6) 196 try: 197 #assume a vbox or hbox 198 self.window.vbox.pack_start(self.notebook, 199 expand=True, fill=True, padding=0) 200 except: 201 #general container instead: 202 self.window.vbox.add(self.notebook) 203 204 self.setup_report_options_frame() 205 self.setup_other_frames() 206 self.notebook.set_current_page(0) 207 208 try: 209 #assume a vbox or hbox 210 self.window.vbox.pack_start(self.grid, 211 expand=True, fill=True, padding=0) 212 except: 213 #general container instead: 214 self.window.vbox.add(self.grid) 215 self.show() 216 217 def get_title(self): 218 """The window title for this dialog""" 219 name = self.report_name 220 category = standalone_categories[self.category][1] 221 return "%s - %s - Gramps" % (name, category) 222 223 #------------------------------------------------------------------------ 224 # 225 # Functions related to extending the options 226 # 227 #------------------------------------------------------------------------ 228 def add_user_options(self): 229 """Called to allow subclasses to add widgets to the dialog form. 230 It is called immediately before the window is displayed. All 231 calls to add_option or add_frame_option should be called in 232 this task.""" 233 add_gui_options(self) 234 235 def parse_user_options(self): 236 """Called to allow parsing of added widgets. 237 It is called when OK is pressed in a dialog. 238 All custom widgets should provide a parsing code here.""" 239 try: 240 self.options.parse_user_options() 241 except: 242 LOG.error("Failed to parse user options.", exc_info=True) 243 244 def add_option(self, label_text, widget): 245 """Takes a text string and a Gtk Widget, and stores them to be 246 appended to the Options section of the dialog. The text string 247 is used to create a label for the passed widget. This allows the 248 subclass to extend the Options section with its own widgets. The 249 subclass is responsible for all managing of the widgets, including 250 extracting the final value before the report executes. This task 251 should only be called in the add_user_options task.""" 252 self.widgets.append((label_text, widget)) 253 254 def add_frame_option(self, frame_name, label_text, widget): 255 """Similar to add_option this method takes a frame_name, a 256 text string and a Gtk Widget. When the interface is built, 257 all widgets with the same frame_name are grouped into a 258 GtkFrame. This allows the subclass to create its own sections, 259 filling them with its own widgets. The subclass is responsible for 260 all managing of the widgets, including extracting the final value 261 before the report executes. This task should only be called in 262 the add_user_options task.""" 263 264 if frame_name in self.frames: 265 self.frames[frame_name].append((label_text, widget)) 266 else: 267 self.frames[frame_name] = [(label_text, widget)] 268 self.frame_names.append(frame_name) 269 270 #------------------------------------------------------------------------ 271 # 272 # Functions to create a default output style. 273 # 274 #------------------------------------------------------------------------ 275 276 def build_style_menu(self, default=None): 277 """Build a menu of style sets that are available for use in 278 this report. This menu will always have a default style 279 available, and will have any other style set name that the 280 user has previously created for this report. This menu is 281 created here instead of inline with the rest of the style 282 frame, because it must be recreated to reflect any changes 283 whenever the user closes the style editor dialog.""" 284 285 if default is None: 286 default = self.style_name 287 288 style_sheet_map = self.style_sheet_list.get_style_sheet_map() 289 self.style_menu.set(style_sheet_map, default) 290 291 #------------------------------------------------------------------------ 292 # 293 # Functions related to setting up the dialog window. 294 # 295 #------------------------------------------------------------------------ 296 def setup_title(self): 297 """Set up the title bar of the dialog. This function relies 298 on the get_title() customization function for what the title 299 should be.""" 300 self.window.set_title(self.get_title()) 301 302 def setup_header(self): 303 """Set up the header line bar of the dialog.""" 304 label = Gtk.Label(label='<span size="larger" weight="bold">%s</span>' % 305 self.report_name) 306 label.set_use_markup(True) 307 self.window.vbox.pack_start(label, False, False, self.border_pad) 308 309 def setup_style_frame(self): 310 """Set up the style frame of the dialog. This function relies 311 on other routines to create the default style for this report, 312 and to read in any user-defined styles for this report. It 313 then builds a menu of all the available styles for the user to 314 choose from.""" 315 # Build the default style set for this report. 316 self.default_style = StyleSheet() 317 self.options.make_default_style(self.default_style) 318 319 if self.default_style.is_empty(): 320 # Don't display the option if no styles are used 321 return 322 323 # Styles Frame 324 label = Gtk.Label(label=_("%s:") % _("Style")) 325 label.set_halign(Gtk.Align.START) 326 327 self.style_menu = StyleComboBox() 328 self.style_menu.set_hexpand(True) 329 self.style_button = Gtk.Button(label="%s..." % _("Style Editor")) 330 self.style_button.connect('clicked', self.on_style_edit_clicked) 331 332 self.grid.attach(label, 1, self.row, 1, 1) 333 self.grid.attach(self.style_menu, 2, self.row, 1, 1) 334 self.grid.attach(self.style_button, 3, self.row, 1, 1) 335 self.row += 1 336 337 # Build the initial list of available styles sets. This 338 # includes the default style set and any style sets saved from 339 # previous invocations of gramps. 340 self.style_sheet_list = StyleSheetList( 341 self.options.handler.get_stylesheet_savefile(), self.default_style) 342 343 # Now build the actual menu. 344 style = self.options.handler.get_default_stylesheet_name() 345 self.build_style_menu(style) 346 347 def setup_report_options_frame(self): 348 """Set up the report options frame of the dialog. This 349 function relies on several report_xxx() customization 350 functions to determine which of the items should be present in 351 this box. *All* of these items are optional, although the 352 generations fields is used in most 353 (but not all) dialog boxes.""" 354 355 row = 0 356 max_rows = len(self.widgets) 357 358 if max_rows == 0: 359 return 360 361 grid = Gtk.Grid() 362 grid.set_border_width(6) 363 grid.set_column_spacing(12) 364 grid.set_row_spacing(6) 365 366 label = Gtk.Label(label="<b>%s</b>" % _("Report Options")) 367 label.set_halign(Gtk.Align.START) 368 label.set_use_markup(True) 369 370 self.notebook.append_page(grid, label) 371 372 # Setup requested widgets 373 for (text, widget) in self.widgets: 374 widget.set_hexpand(True) 375 if text: 376 # translators: needed for French, ignore otherwise 377 text_widget = Gtk.Label(label=_("%s:") % text) 378 text_widget.set_halign(Gtk.Align.START) 379 grid.attach(text_widget, 1, row, 1, 1) 380 grid.attach(widget, 2, row, 1, 1) 381 else: 382 grid.attach(widget, 2, row, 1, 1) 383 row += 1 384 385 def setup_other_frames(self): 386 from .._guioptions import GuiTextOption 387 for key in self.frame_names: 388 flist = self.frames[key] 389 grid = Gtk.Grid() 390 grid.set_column_spacing(12) 391 grid.set_row_spacing(6) 392 grid.set_border_width(6) 393 l = Gtk.Label(label="<b>%s</b>" % _(key)) 394 l.set_use_markup(True) 395 self.notebook.append_page(grid, l) 396 397 row = 0 398 for (text, widget) in flist: 399 widget.set_hexpand(True) 400 if text: 401 text_widget = Gtk.Label(label=_('%s:') % text) 402 text_widget.set_halign(Gtk.Align.START) 403 grid.attach(text_widget, 1, row, 1, 1) 404 if isinstance(widget, GuiTextOption): 405 grid.attach(widget, 2, row, 1, 1) 406 else: 407 grid.attach(widget, 2, row, 1, 1) 408 else: 409 grid.attach(widget, 2, row, 1, 1) 410 row += 1 411 412 #------------------------------------------------------------------------ 413 # 414 # Customization hooks for stand-alone reports (subclass ReportDialog) 415 # 416 #------------------------------------------------------------------------ 417 def setup_format_frame(self): 418 """Not used in bare report dialogs. Override in the subclass.""" 419 pass 420 421 #------------------------------------------------------------------------ 422 # 423 # Functions related getting/setting the default directory for a dialog. 424 # 425 #------------------------------------------------------------------------ 426 def get_default_directory(self): 427 """Get the name of the directory to which the target dialog 428 box should default. This value can be set in the preferences 429 panel.""" 430 return config.get('paths.report-directory') 431 432 def set_default_directory(self, value): 433 """Save the name of the current directory, so that any future 434 reports will default to the most recently used directory. 435 This also changes the directory name that will appear in the 436 preferences panel, but does not change the preference in disk. 437 This means that the last directory used will only be 438 remembered for this session of gramps unless the user saves 439 his/her preferences.""" 440 config.set('paths.report-directory', value) 441 442 #------------------------------------------------------------------------ 443 # 444 # Functions related to setting up the dialog window. 445 # 446 #------------------------------------------------------------------------ 447 def setup_init(self): 448 # add any elements that we are going to need: 449 hid = self.style_name 450 if hid[-4:] == ".xml": 451 hid = hid[0:-4] 452 self.target_fileentry = FileEntry(hid, _("Save As"), 453 parent=self.window) 454 spath = self.get_default_directory() 455 self.target_fileentry.set_filename(spath) 456 # need any labels at top: 457 label = Gtk.Label(label="<b>%s</b>" % _('Document Options')) 458 label.set_use_markup(1) 459 label.set_halign(Gtk.Align.START) 460 self.grid.set_border_width(12) 461 self.grid.attach(label, 0, self.row, 4, 1) 462 self.row += 1 463 464 def setup_target_frame(self): 465 """Set up the target frame of the dialog. This function 466 relies on several target_xxx() customization functions to 467 determine whether the target is a directory or file, what the 468 title of any browser window should be, and what default 469 directory should be used.""" 470 471 # Save Frame 472 self.doc_label = Gtk.Label(label=_("%s:") % _("Filename")) 473 self.doc_label.set_halign(Gtk.Align.START) 474 475 self.grid.attach(self.doc_label, 1, self.row, 1, 1) 476 self.target_fileentry.set_hexpand(True) 477 self.grid.attach(self.target_fileentry, 2, self.row, 2, 1) 478 self.row += 1 479 480 #------------------------------------------------------------------------ 481 # 482 # Functions related to retrieving data from the dialog window 483 # 484 #------------------------------------------------------------------------ 485 def parse_target_frame(self): 486 """Parse the target frame of the dialog. If the target 487 filename is empty this routine returns a special value of None 488 to tell the calling routine to give up. This function also 489 saves the current directory so that any future reports will 490 default to the most recently used directory.""" 491 self.target_path = self.target_fileentry.get_full_path(0) 492 if not self.target_path: 493 return None 494 495 # First we check whether the selected path exists 496 if os.path.exists(self.target_path): 497 498 # selected path is an existing dir and we need a dir 499 if os.path.isdir(self.target_path): 500 501 # check whether the dir has rwx permissions 502 if not os.access(self.target_path, os.R_OK|os.W_OK|os.X_OK): 503 ErrorDialog(_('Permission problem'), 504 _("You do not have permission to write " 505 "under the directory %s\n\n" 506 "Please select another directory or correct " 507 "the permissions.") % self.target_path, 508 parent=self.window) 509 return None 510 511 # selected path is an existing file and we need a file 512 if os.path.isfile(self.target_path): 513 aaa = OptionDialog(_('File already exists'), 514 _('You can choose to either overwrite the ' 515 'file, or change the selected filename.'), 516 _('_Overwrite'), None, 517 _('_Change filename'), None, 518 parent=self.window) 519 520 if aaa.get_response() == Gtk.ResponseType.YES: 521 return None 522 523 # selected path does not exist yet 524 else: 525 # we will need to create the file/dir 526 # need to make sure we can create in the parent dir 527 parent_dir = os.path.dirname(os.path.normpath(self.target_path)) 528 if os.path.isdir(parent_dir): 529 if not os.access(parent_dir, os.W_OK): 530 ErrorDialog(_('Permission problem'), 531 _("You do not have permission to create " 532 "%s\n\n" 533 "Please select another path or correct " 534 "the permissions.") % self.target_path, 535 parent=self.window) 536 return None 537 else: 538 ErrorDialog(_('No directory'), 539 _('There is no directory %s.\n\n' 540 'Please select another directory ' 541 'or create it.') % parent_dir, 542 parent=self.window) 543 return None 544 545 self.set_default_directory(os.path.dirname(self.target_path) + os.sep) 546 self.options.handler.output = self.target_path 547 return 1 548 549 def parse_style_frame(self): 550 """Parse the style frame of the dialog. Save the user 551 selected output style for later use. Note that this routine 552 retrieves a value whether or not the menu is displayed on the 553 screen. The subclass will know whether this menu was enabled. 554 This is for simplicity of programming.""" 555 if not self.default_style.is_empty(): 556 (style_name, self.selected_style) = self.style_menu.get_value() 557 self.options.handler.set_default_stylesheet_name(style_name) 558 559 #------------------------------------------------------------------------ 560 # 561 # Callback functions from the dialog 562 # 563 #------------------------------------------------------------------------ 564 def on_ok_clicked(self, obj): 565 """The user is satisfied with the dialog choices. Validate 566 the output file name before doing anything else. If there is 567 a file name, gather the options and create the report.""" 568 569 # Is there a filename? This should also test file permissions, etc. 570 if not self.parse_target_frame(): 571 self.window.run() 572 573 # Preparation 574 self.parse_style_frame() 575 self.parse_user_options() 576 577 # Save options 578 self.options.handler.save_options() 579 580 def on_cancel(self, *obj): 581 pass 582 583 def on_help_clicked(self, *obj): 584 from ...display import display_help, display_url 585 if hasattr(self.options, 'help_url'): 586 display_url(self.options.help_url) 587 return 588 display_help(URL_REPORT_PAGE, self.report_name.replace(" ", "_")) 589 590 def on_style_edit_clicked(self, *obj): 591 """The user has clicked on the 'Edit Styles' button. Create a 592 style sheet editor object and let them play. When they are 593 done, the previous routine will be called to update the dialog 594 menu for selecting a style.""" 595 StyleListDisplay(self.style_sheet_list, self.uistate, self.track, 596 callback=self.build_style_menu) 597 598 #---------------------------------------------------------------------- 599 # 600 # Functions related to any docgen options for a dialog. 601 # 602 #---------------------------------------------------------------------- 603 def setup_doc_options_frame(self): 604 if self.doc_widgets: 605 for option_widget in self.doc_widgets: 606 self.grid.remove(option_widget) 607 self.doc_widgets = [] 608 self.doc_options = None 609 610 if not self.doc_option_class: 611 return # this docgen type has no options 612 613 self.init_doc_options(self.doc_option_class) 614 menu = self.doc_options.menu 615 for name in menu.get_option_names(DOCGEN_OPTIONS): 616 option = menu.get_option(DOCGEN_OPTIONS, name) 617 # override option default with xml-saved value: 618 if name in self.doc_options.options_dict: 619 option.set_value(self.doc_options.options_dict[name]) 620 widget, has_label = make_gui_option(option, self.dbstate, 621 self.uistate, self.track) 622 if has_label: 623 widget_text = Gtk.Label(label=(_('%s:') % option.get_label())) 624 widget_text.set_halign(Gtk.Align.START) 625 self.grid.attach(widget_text, 1, self.row, 1, 1) 626 self.doc_widgets.append(widget_text) 627 self.grid.attach(widget, 2, self.row, 2, 1) 628 self.doc_widgets.append(widget) 629 self.row += 1 630 631 def init_doc_options(self, option_class): 632 try: 633 if issubclass(option_class, object): 634 self.doc_options = option_class(self.raw_name, self.db) 635 except TypeError: 636 self.doc_options = option_class 637 self.doc_options.load_previous_values() 638 639 def parse_doc_options(self): 640 """ 641 Called to allow parsing of added docgen widgets. 642 It is called when OK is pressed in a dialog. 643 """ 644 if not self.doc_options: 645 return 646 try: 647 self.doc_options.parse_user_options() 648 for opt in self.doc_options.options_dict: 649 self.options.options_dict[opt] = \ 650 [self.basedocname, self.doc_options.options_dict[opt]] 651 except: 652 logging.warning("Failed to parse doc options") 653 654#------------------------------------------------------------------------ 655# 656# Generic task function a standalone GUI report 657# 658#------------------------------------------------------------------------ 659def report(dbstate, uistate, person, report_class, options_class, 660 trans_name, name, category, require_active): 661 """ 662 report - task starts the report. The plugin system requires that the 663 task be in the format of task that takes a database and a person as 664 its arguments. 665 """ 666 if require_active and not person: 667 ErrorDialog( 668 _('Active person has not been set'), 669 _('You must select an active person for this report to work ' 670 'properly.'), 671 parent=uistate.window) 672 return 673 674 if category == CATEGORY_TEXT: 675 from ._textreportdialog import TextReportDialog 676 dialog_class = TextReportDialog 677 elif category == CATEGORY_DRAW: 678 from ._drawreportdialog import DrawReportDialog 679 dialog_class = DrawReportDialog 680 elif category == CATEGORY_GRAPHVIZ: 681 from ._graphvizreportdialog import GraphvizReportDialog 682 dialog_class = GraphvizReportDialog 683 elif category == CATEGORY_TREE: 684 from ._treereportdialog import TreeReportDialog 685 dialog_class = TreeReportDialog 686 elif category == CATEGORY_WEB: 687 from ._webreportdialog import WebReportDialog 688 dialog_class = WebReportDialog 689 elif category in (CATEGORY_BOOK, CATEGORY_CODE): 690 try: 691 report_class(dbstate, uistate) 692 except WindowActiveError: 693 pass 694 return 695 else: 696 dialog_class = ReportDialog 697 698 dialog = dialog_class(dbstate, uistate, options_class, name, trans_name) 699 700 while True: 701 response = dialog.window.run() 702 if response == Gtk.ResponseType.OK: 703 dialog.close() 704 try: 705 user = User(uistate=uistate) 706 my_report = report_class(dialog.db, dialog.options, user) 707 my_report.doc.init() 708 my_report.begin_report() 709 my_report.write_report() 710 my_report.end_report() 711 712 # Web reports do not have a target frame 713 # The GtkPrint generator can not be "opened" 714 if (hasattr(dialog, "open_with_app") and 715 dialog.open_with_app.get_property('sensitive') == True 716 and dialog.open_with_app.get_active()): 717 out_file = dialog.options.get_output() 718 open_file_with_default_application(out_file, uistate) 719 720 except FilterError as msg: 721 (msg1, msg2) = msg.messages() 722 ErrorDialog(msg1, msg2, parent=uistate.window) 723 except IOError as msg: 724 ErrorDialog(_("Report could not be created"), 725 str(msg), 726 parent=uistate.window) 727 except ReportError as msg: 728 (msg1, msg2) = msg.messages() 729 ErrorDialog(msg1, msg2, parent=uistate.window) 730 except DatabaseError as msg: 731 ErrorDialog(_("Report could not be created"), 732 str(msg), 733 parent=uistate.window) 734# The following except statement will catch all "NoneType" exceptions. 735# This is useful for released code where the exception is most likely 736# a corrupt database. But it is less useful for developing new reports 737# where the exception is most likely a report bug. 738# except AttributeError,msg: 739# if str(msg).startswith("'NoneType' object has no attribute"): 740# # "'NoneType' object has no attribute ..." usually means 741# # database corruption 742# RunDatabaseRepair(str(msg), 743# parent=self.window) 744# else: 745# raise 746 raise 747 except: 748 LOG.error("Failed to run report.", exc_info=True) 749 break 750 elif response == Gtk.ResponseType.CANCEL: 751 dialog.close() 752 break 753 elif response == Gtk.ResponseType.DELETE_EVENT: 754 #just stop, in ManagedWindow, delete-event is already coupled to 755 #correct action. 756 break 757 758 #do needed cleanup 759 dialog.db = None 760 dialog.options = None 761 if hasattr(dialog, 'window'): 762 delattr(dialog, 'window') 763 if hasattr(dialog, 'notebook'): 764 delattr(dialog, 'notebook') 765 del dialog 766