1#
2# Gramps - a GTK+/GNOME based genealogy program
3#
4# Copyright (C) 2003-2006  Donald N. Allingham
5# Copyright (C) 2008       Brian G. Matherly
6# Copyright (C) 2010       Jakim Friant
7#
8# This program is free software; you can redistribute it and/or modify
9# it under the terms of the GNU General Public License as published by
10# the Free Software Foundation; either version 2 of the License, or
11# (at your option) any later version.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16# GNU General Public License for more details.
17#
18# You should have received a copy of the GNU General Public License
19# along with this program; if not, write to the Free Software
20# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21#
22
23"""
24Show uncollected objects in a window.
25"""
26#------------------------------------------------------------------------
27#
28# Python modules
29#
30#------------------------------------------------------------------------
31import weakref
32import sys
33#------------------------------------------------------------------------
34#
35# GNOME/GTK modules
36#
37#------------------------------------------------------------------------
38from gi.repository import Gtk
39from gi.repository import Gdk
40import gc
41
42#------------------------------------------------------------------------
43#
44# Gramps modules
45#
46#------------------------------------------------------------------------
47from gramps.gen.plug import Gramplet
48from gramps.gui.dialog import InfoDialog
49from gramps.gui.utils import is_right_click, ProgressMeter
50from gramps.gen.const import GRAMPS_LOCALE as glocale
51_ = glocale.translation.gettext
52
53
54#-------------------------------------------------------------------------
55#
56# Leak
57#
58#-------------------------------------------------------------------------
59class Leak(Gramplet):
60    """
61    Shows uncollected objects.
62    """
63    def init(self):
64        self.gui.WIDGET = self.build_gui()
65        self.gui.get_container_widget().remove(self.gui.textview)
66        self.gui.get_container_widget().add(self.gui.WIDGET)
67
68        flags = gc.DEBUG_UNCOLLECTABLE | gc.DEBUG_SAVEALL
69        if hasattr(gc, "DEBUG_OBJECTS"):
70            flags = flags | gc.DEBUG_OBJECTS
71        gc.set_debug(flags)
72
73    def build_gui(self):
74        """
75        Build the GUI interface.
76        """
77        self.top = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
78        self.top.set_border_width(6)
79
80        self.label = Gtk.Label(halign=Gtk.Align.START)
81        self.top.pack_start(self.label, False, False, 6)
82
83        self.scroll = Gtk.ScrolledWindow()
84        # add a listview to the scrollable
85        self.list = Gtk.TreeView()
86        self.list.set_headers_visible(True)
87        self.list.connect('button-press-event', self._button_press)
88        self.scroll.add(self.list)
89        # make a model
90        self.model = Gtk.ListStore(int, str, str)
91        self.list.set_model(self.model)
92
93        # set the columns
94        self.renderer = Gtk.CellRendererText()
95        column = Gtk.TreeViewColumn(_('Number'), self.renderer, text=0)
96        column.set_resizable(True)
97        column.set_sizing(Gtk.TreeViewColumnSizing.FIXED)
98        self.list.append_column(column)
99        column = Gtk.TreeViewColumn(_('Referrer'), self.renderer, text=1)
100        column.set_resizable(True)
101        column.set_sizing(Gtk.TreeViewColumnSizing.FIXED)
102        self.list.append_column(column)
103        column = Gtk.TreeViewColumn(_('Uncollected object'), self.renderer,
104                                    text=2)
105        column.set_resizable(True)
106        column.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE)
107        self.list.append_column(column)
108        self.selection = self.list.get_selection()
109        self.top.pack_start(self.scroll, True, True, 6)
110
111        bbox = Gtk.ButtonBox()
112        apply_button = Gtk.Button(label=_("Refresh"))
113        apply_button.connect('clicked', self.apply_clicked)
114        bbox.pack_start(apply_button, False, False, 6)
115        self.top.pack_start(bbox, False, False, 6)
116
117        self.top.show_all()
118
119        return self.top
120
121    def main(self):
122        self.label.set_text(_('Press Refresh to see initial results'))
123        self.model.clear()
124        # self.display()    # We should only run this on demand
125
126    def _button_press(self, obj, event):
127        if (event.type == Gdk.EventType.DOUBLE_BUTTON_PRESS
128                and event.button == 1):
129            self.referenced_in()
130            return True
131        elif is_right_click(event):
132            self.refers_to()
133            return True
134
135    def referenced_in(self):
136        model, _iter = self.selection.get_selected()
137        if _iter is not None:
138            count = model.get_value(_iter, 0)
139            gc.collect(2)
140            referrers = gc.get_referrers(self.junk[count])
141            text = ""
142            for referrer in referrers:
143                match = ""
144                try:
145                    if referrer is not self.junk:
146                        match = "**** "
147                        for indx in range(len(self.junk)):
148                            if referrer is self.junk[indx]:
149                                match = str(indx) + ": "
150                                break
151                        match += str(referrer) + '\n'
152                except ReferenceError:
153                    match += 'weakly-referenced object no longer exists %s'\
154                        % type(referrer)
155                except:
156                    print(sys.exc_info())
157                text += match
158            InfoDialog(_('Referrers of %d') % count, text, parent=self.parent)
159
160    def refers_to(self):
161        model, _iter = self.selection.get_selected()
162        if _iter is not None:
163            count = model.get_value(_iter, 0)
164            referents = gc.get_referents(self.junk[count])
165            text = ""
166            for referent in referents:
167                match = ""
168                try:
169                    match = "****: "
170                    for indx in range(len(self.junk)):
171                        if referent is self.junk[indx]:
172                            match = str(indx) + ': '
173                            break
174                    match += str(referent) + '\n'
175                except ReferenceError:
176                    match += '%s weakly-referenced object no longer'\
177                            ' exists\n' % type(referent)
178                except:
179                    print(sys.exc_info())
180                text += match
181            InfoDialog(_('%d refers to') % count, text, parent=self.parent)
182
183    def display(self):
184        try:
185            from bsddb3.db import DBError
186        except:
187            class DBError(Exception):
188                """
189                Dummy.
190                """
191        self.parent = self.top.get_toplevel()
192        progress = ProgressMeter(
193            _('Updating display...'), '', parent=self.parent, can_cancel=True)
194        self.model.clear()
195        self.junk = []
196        gc.collect(2)
197        self.junk = gc.garbage
198        self.label.set_text(_('Uncollected Objects: %s') %
199                            str(len(self.junk)))
200        progress.set_pass(_('Updating display...'), len(self.junk))
201        for count in range(0, len(self.junk)):
202            if progress.step():
203                break
204            try:
205                refs = []
206                referrers = gc.get_referrers(self.junk[count])
207                for referrer in referrers:
208                    try:
209                        if referrer is not self.junk:
210                            for indx in range(0, len(self.junk)):
211                                if referrer is self.junk[indx]:
212                                    refs.append(str(indx) + ' ')
213                                    break
214                    except:
215                        print(sys.exc_info())
216                if len(refs) > 3:
217                    ref = ' '.join(refs[0:2]) + "..."
218                else:
219                    ref = ' '.join(refs)
220                try:
221                    self.model.append((count, ref, str(self.junk[count])))
222                except DBError:
223                    self.model.append((count, ref,
224                                      'db.DB instance at %s' %
225                                       id(self.junk[count])))
226                except ReferenceError:
227                    self.model.append((
228                        count, ref,
229                        'weakly-referenced object no longer exists %s'
230                        % type(self.junk[count])))
231                except TypeError:
232                    self.model.append((
233                        count, ref,
234                        'Object cannot be displayed %s'
235                        % type(self.junk[count])))
236                except:
237                    print(sys.exc_info())
238            except ReferenceError:
239                InfoDialog(_('Reference Error'), "Refresh to correct",
240                           parent=self.parent)
241        progress.close()
242
243    def apply_clicked(self, obj):
244        self.display()
245