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