[hamster-applet] fighting memory leaks. fixes all the can't-open-window-second-time bugs



commit 41787b195546c3b673e0c38681ed9e07555a554b
Author: Toms BauÄis <toms baugis gmail com>
Date:   Sat Apr 7 18:05:09 2012 +0300

    fighting memory leaks. fixes all the can't-open-window-second-time bugs

 src/hamster/configuration.py               |   14 ++++++-
 src/hamster/edit_activity.py               |   18 +++++++--
 src/hamster/overview.py                    |   32 ++++++++++-----
 src/hamster/preferences.py                 |   56 +++++++++++++++++++++------
 src/hamster/stats.py                       |   25 ++++++++----
 src/hamster/widgets/activityentry.py       |   13 ++++++-
 src/hamster/widgets/dateinput.py           |    5 ++
 src/hamster/widgets/facttree.py            |    7 +++
 src/hamster/widgets/rangepick.py           |    5 ++
 src/hamster/widgets/reportchooserdialog.py |    2 +
 src/hamster/widgets/tags.py                |   11 +++++-
 src/hamster/widgets/timeinput.py           |    4 ++
 12 files changed, 151 insertions(+), 41 deletions(-)
---
diff --git a/src/hamster/configuration.py b/src/hamster/configuration.py
index 5d61682..f301ef5 100644
--- a/src/hamster/configuration.py
+++ b/src/hamster/configuration.py
@@ -83,6 +83,15 @@ class OneWindow(object):
     def __init__(self, get_dialog_class):
         self.dialogs = {}
         self.get_dialog_class = get_dialog_class
+        self.dialog_close_handlers = {}
+
+    def on_close_window(self, dialog):
+        for key, assoc_dialog in list(self.dialogs.iteritems()):
+            if dialog == assoc_dialog:
+                del self.dialogs[key]
+
+        handler = self.dialog_close_handlers.pop(dialog)
+        dialog.disconnect(handler)
 
 
     def show(self, parent = None, **kwargs):
@@ -90,8 +99,7 @@ class OneWindow(object):
 
         if params in self.dialogs:
             window = self.dialogs[params].window
-            if not window.get_visible():
-                self.dialogs[params].show()
+            self.dialogs[params].show()
             window.present()
         else:
             if parent:
@@ -99,6 +107,8 @@ class OneWindow(object):
 
                 if isinstance(parent, gtk.Widget):
                     dialog.window.set_transient_for(parent.get_toplevel())
+
+                self.dialog_close_handlers[dialog] = dialog.connect("on-close", self.on_close_window)
             else:
                 dialog = self.get_dialog_class()(**kwargs)
 
diff --git a/src/hamster/edit_activity.py b/src/hamster/edit_activity.py
index 1d65dd4..fac5854 100644
--- a/src/hamster/edit_activity.py
+++ b/src/hamster/edit_activity.py
@@ -17,7 +17,7 @@
 # You should have received a copy of the GNU General Public License
 # along with Project Hamster.  If not, see <http://www.gnu.org/licenses/>.
 
-import gtk
+import gtk, gobject
 import time
 import datetime as dt
 
@@ -28,8 +28,13 @@ import widgets
 from configuration import runtime, conf, load_ui_file
 from lib import stuff
 
-class CustomFactController:
+class CustomFactController(gtk.Object):
+    __gsignals__ = {
+        "on-close": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
+    }
+
     def __init__(self,  parent = None, fact_date = None, fact_id = None):
+        gtk.Object.__init__(self)
 
         self._gui = load_ui_file("edit_activity.ui")
         self.window = self.get_widget('custom_fact_window')
@@ -307,5 +312,10 @@ class CustomFactController:
         self.close_window()
 
     def close_window(self):
-        self.window.destroy()
-        return False
+        if not self.parent:
+            gtk.main_quit()
+        else:
+            self.window.destroy()
+            self.window = None
+            self._gui = None
+            self.emit("on-close")
diff --git a/src/hamster/overview.py b/src/hamster/overview.py
index 6eeb472..9c1556a 100644
--- a/src/hamster/overview.py
+++ b/src/hamster/overview.py
@@ -38,8 +38,14 @@ from overview_activities import OverviewBox
 from overview_totals import TotalsBox
 
 
-class Overview(object):
+class Overview(gtk.Object):
+    __gsignals__ = {
+        "on-close": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
+    }
+
     def __init__(self, parent = None):
+        gtk.Object.__init__(self)
+
         self.parent = parent# determine if app should shut down on close
         self._gui = load_ui_file("overview.ui")
         self.report_chooser = None
@@ -66,10 +72,12 @@ class Overview(object):
         self.get_widget("by_day_box").add(self.timechart)
 
         self._gui.connect_signals(self)
-        runtime.storage.connect('activities-changed',self.after_activity_update)
-        runtime.storage.connect('facts-changed',self.after_activity_update)
 
-        conf.connect('conf-changed', self.on_conf_change)
+        self.external_listeners = [
+            (runtime.storage, runtime.storage.connect('activities-changed',self.after_activity_update)),
+            (runtime.storage, runtime.storage.connect('facts-changed',self.after_activity_update)),
+            (conf, conf.connect('conf-changed', self.on_conf_change))
+        ]
         self.show()
 
 
@@ -192,9 +200,6 @@ class Overview(object):
         return True
 
     def after_activity_update(self, widget):
-        if not self.window.get_visible():
-            return
-
         self.search()
 
 
@@ -416,11 +421,16 @@ class Overview(object):
         if not self.parent:
             gtk.main_quit()
         else:
-            self.window.hide()
-            self.facts = None
-            return False
+            for obj, handler in self.external_listeners:
+                obj.disconnect(handler)
+            self._gui = None
+            self.window.destroy()
+            self.window = None
+
+            self.emit("on-close")
+
+
 
     def on_delete_window(self, window, event):
         self.close_window()
         return True
-
diff --git a/src/hamster/preferences.py b/src/hamster/preferences.py
index a080146..5dfa4d4 100755
--- a/src/hamster/preferences.py
+++ b/src/hamster/preferences.py
@@ -91,14 +91,19 @@ from lib import stuff, trophies
 
 
 
-class PreferencesEditor:
+class PreferencesEditor(gtk.Object):
     TARGETS = [
         ('MY_TREE_MODEL_ROW', gtk.TARGET_SAME_WIDGET, 0),
         ('MY_TREE_MODEL_ROW', gtk.TARGET_SAME_APP, 0),
         ]
 
 
+    __gsignals__ = {
+        "on-close": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
+    }
+
     def __init__(self, parent = None):
+        gtk.Object.__init__(self)
         self.parent = parent
         self._gui = load_ui_file("preferences.ui")
         self.window = self.get_widget('preferences_window')
@@ -120,10 +125,14 @@ class PreferencesEditor:
         self.get_widget("activities_label").set_mnemonic_widget(self.activity_tree)
         self.activity_store = ActivityStore()
 
+        self.external_listeners = []
+
         self.activityColumn = gtk.TreeViewColumn(_("Name"))
         self.activityColumn.set_expand(True)
         self.activityCell = gtk.CellRendererText()
-        self.activityCell.connect('edited', self.activity_name_edited_cb, self.activity_store)
+        self.external_listeners.extend([
+            (self.activityCell, self.activityCell.connect('edited', self.activity_name_edited_cb, self.activity_store))
+        ])
         self.activityColumn.pack_start(self.activityCell, True)
         self.activityColumn.set_attributes(self.activityCell, text=1)
         self.activityColumn.set_sort_column_id(1)
@@ -132,7 +141,10 @@ class PreferencesEditor:
         self.activity_tree.set_model(self.activity_store)
 
         self.selection = self.activity_tree.get_selection()
-        self.selection.connect('changed', self.activity_changed, self.activity_store)
+
+        self.external_listeners.extend([
+            (self.selection, self.selection.connect('changed', self.activity_changed, self.activity_store))
+        ])
 
 
         # create and fill category tree
@@ -143,7 +155,10 @@ class PreferencesEditor:
         self.categoryColumn = gtk.TreeViewColumn(_("Category"))
         self.categoryColumn.set_expand(True)
         self.categoryCell = gtk.CellRendererText()
-        self.categoryCell.connect('edited', self.category_edited_cb, self.category_store)
+
+        self.external_listeners.extend([
+            (self.categoryCell, self.categoryCell.connect('edited', self.category_edited_cb, self.category_store))
+        ])
 
         self.categoryColumn.pack_start(self.categoryCell, True)
         self.categoryColumn.set_attributes(self.categoryCell, text=1)
@@ -155,11 +170,12 @@ class PreferencesEditor:
         self.category_tree.set_model(self.category_store)
 
         selection = self.category_tree.get_selection()
-        selection.connect('changed', self.category_changed_cb, self.category_store)
+        self.external_listeners.extend([
+            (selection, selection.connect('changed', self.category_changed_cb, self.category_store))
+        ])
 
         self.day_start = widgets.TimeInput(dt.time(5,30))
         self.get_widget("day_start_placeholder").add(self.day_start)
-        self.day_start.connect("time-entered", self.on_day_start_changed)
 
 
         self.load_config()
@@ -175,8 +191,7 @@ class PreferencesEditor:
 
         self.activity_tree.connect("drag_data_get", self.drag_data_get_data)
 
-        self.category_tree.connect("drag_data_received",
-                                   self.on_category_drop)
+        self.category_tree.connect("drag_data_received", self.on_category_drop)
 
         #select first category
         selection = self.category_tree.get_selection()
@@ -198,7 +213,10 @@ class PreferencesEditor:
         self.wActivityColumn.set_expand(True)
         self.wActivityCell = gtk.CellRendererText()
         self.wActivityCell.set_property('editable', True)
-        self.wActivityCell.connect('edited', self.on_workspace_activity_edited)
+
+        self.external_listeners.extend([
+            (self.wActivityCell, self.wActivityCell.connect('edited', self.on_workspace_activity_edited))
+        ])
 
         self.wNameColumn.pack_start(self.wNameCell, True)
         self.wNameColumn.set_attributes(self.wNameCell)
@@ -219,14 +237,20 @@ class PreferencesEditor:
             self.get_widget("notification_preference_frame").hide()
 
 
+        self.external_listeners.extend([
+            (self.day_start, self.day_start.connect("time-entered", self.on_day_start_changed))
+        ])
+
         # disable workspace tracking if wnck is not there
         if wnck:
             self.screen = wnck.screen_get_default()
             for workspace in self.screen.get_workspaces():
                 self.on_workspace_created(self.screen, workspace)
 
-            self.screen.workspace_add_handler = self.screen.connect("workspace-created", self.on_workspace_created)
-            self.screen.workspace_del_handler = self.screen.connect("workspace-destroyed", self.on_workspace_deleted)
+            self.external_listeners.extend([
+                (self.screen, self.screen.connect("workspace-created", self.on_workspace_created)),
+                (self.screen, self.screen.connect("workspace-destroyed", self.on_workspace_deleted))
+            ])
         else:
             self.get_widget("workspace_tab").hide()
 
@@ -679,8 +703,14 @@ class PreferencesEditor:
         if not self.parent:
             gtk.main_quit()
         else:
-            self.window.hide()
-            return False
+            for obj, handler in self.external_listeners:
+                obj.disconnect(handler)
+            self.window.destroy()
+            self.window = None
+            self._gui = None
+            self.wNameColumn = None
+            self.categoryColumn = None
+            self.emit("on-close")
 
     def on_workspace_tracking_toggled(self, checkbox):
         workspace_tracking = []
diff --git a/src/hamster/stats.py b/src/hamster/stats.py
index 073749f..1d1c880 100644
--- a/src/hamster/stats.py
+++ b/src/hamster/stats.py
@@ -38,8 +38,13 @@ from configuration import runtime, conf, load_ui_file
 
 from lib.i18n import C_
 
-class Stats(object):
+class Stats(gtk.Object):
+    __gsignals__ = {
+        "on-close": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
+    }
+
     def __init__(self, parent = None):
+        gtk.Object.__init__(self)
         self._gui = load_ui_file("stats.ui")
         self.report_chooser = None
         self.window = self.get_widget("stats_window")
@@ -52,10 +57,7 @@ class Stats(object):
         self.get_widget("explore_everything").add(self.timechart)
         self.get_widget("explore_everything").show_all()
 
-        runtime.storage.connect('activities-changed',self.after_fact_update)
-        runtime.storage.connect('facts-changed',self.after_fact_update)
         self.window.set_position(gtk.WIN_POS_CENTER)
-
         self.chart_category_totals = charting.Chart(value_format = "%.1f",
                                                        max_bar_width = 20,
                                                        legend_width = 70,
@@ -104,6 +106,10 @@ class Stats(object):
         self.get_widget("explore_summary").add(self.explore_summary)
         self.get_widget("explore_summary").show_all()
 
+        self.external_listeners = [
+            (runtime.storage, runtime.storage.connect('activities-changed',self.after_fact_update)),
+            (runtime.storage, runtime.storage.connect('facts-changed',self.after_fact_update))
+        ]
 
         self._gui.connect_signals(self)
         self.show()
@@ -423,9 +429,6 @@ than 15 minutes, you seem to be a busy bee.") % ("<b>%d</b>" % short_percent)
 
 
     def after_fact_update(self, event):
-        if not self.window.get_visible():
-            return
-
         self.stat_facts = runtime.storage.get_facts(dt.date(1970, 1, 1), dt.date.today())
         self.stats()
 
@@ -447,8 +450,12 @@ than 15 minutes, you seem to be a busy bee.") % ("<b>%d</b>" % short_percent)
         if not self.parent:
             gtk.main_quit()
         else:
-            self.window.hide()
-            return False
+            for obj, handler in self.external_listeners:
+                obj.disconnect(handler)
+            self.window.destroy()
+            self.window = None
+            self._gui = None
+            self.emit("on-close")
 
 
 if __name__ == "__main__":
diff --git a/src/hamster/widgets/activityentry.py b/src/hamster/widgets/activityentry.py
index 7e0389b..0e82c3e 100644
--- a/src/hamster/widgets/activityentry.py
+++ b/src/hamster/widgets/activityentry.py
@@ -95,11 +95,22 @@ class ActivityEntry(gtk.Entry):
         self.connect("changed", self._on_text_changed)
         self._parent_click_watcher = None # bit lame but works
 
-        runtime.storage.connect('activities-changed',self.after_activity_update)
+        self.external_listeners = [
+            (runtime.storage, runtime.storage.connect('activities-changed',self.after_activity_update)),
+        ]
 
         self.show()
         self.populate_suggestions()
 
+        self.connect("destroy", self.on_destroy)
+
+    def on_destroy(self, window):
+        for obj, handler in self.external_listeners:
+            obj.disconnect(handler)
+
+        self.popup.destroy()
+        self.popup = None
+
     def get_value(self):
         activity_name = self.get_text().decode("utf-8").strip()
         if not activity_name:
diff --git a/src/hamster/widgets/dateinput.py b/src/hamster/widgets/dateinput.py
index 37cede8..f1512bf 100644
--- a/src/hamster/widgets/dateinput.py
+++ b/src/hamster/widgets/dateinput.py
@@ -61,6 +61,11 @@ class DateInput(gtk.Entry):
 
         self.connect("changed", self._on_text_changed)
         self.show()
+        self.connect("destroy", self.on_destroy)
+
+    def on_destroy(self, window):
+        self.popup.destroy()
+        self.popup = None
 
     def set_date(self, date):
         """sets date to specified, using default format"""
diff --git a/src/hamster/widgets/facttree.py b/src/hamster/widgets/facttree.py
index 009b74b..02e7894 100644
--- a/src/hamster/widgets/facttree.py
+++ b/src/hamster/widgets/facttree.py
@@ -144,6 +144,13 @@ class FactTree(gtk.TreeView):
         self.prev_rows = []
         self.new_rows = []
 
+        self.connect("destroy", self.on_destroy)
+
+    def on_destroy(self, widget):
+        for col in self.get_columns():
+            col.destroy()
+            self.remove_column(col)
+
 
     def fix_row_heights(self):
         alloc = self.get_allocation()
diff --git a/src/hamster/widgets/rangepick.py b/src/hamster/widgets/rangepick.py
index a9abb74..22ce59f 100644
--- a/src/hamster/widgets/rangepick.py
+++ b/src/hamster/widgets/rangepick.py
@@ -54,7 +54,12 @@ class RangePick(gtk.ToggleButton):
         self.connect("toggled", self.on_toggle)
 
         self._gui.connect_signals(self)
+        self.connect("destroy", self.on_destroy)
 
+    def on_destroy(self, window):
+        self.popup.destroy()
+        self.popup = None
+        self._gui = None
 
     def on_toggle(self, button):
         if self.get_active():
diff --git a/src/hamster/widgets/reportchooserdialog.py b/src/hamster/widgets/reportchooserdialog.py
index 529edab..2e969df 100644
--- a/src/hamster/widgets/reportchooserdialog.py
+++ b/src/hamster/widgets/reportchooserdialog.py
@@ -109,6 +109,7 @@ class ReportChooserDialog(gtk.Dialog):
         if response != gtk.RESPONSE_OK:
             self.emit("report-chooser-closed")
             self.dialog.destroy()
+            self.dialog = None
         else:
             self.on_save_button_clicked()
 
@@ -137,3 +138,4 @@ class ReportChooserDialog(gtk.Dialog):
         # format, path, start_date, end_date
         self.emit("report-chosen", format, path)
         self.dialog.destroy()
+        self.dialog = None
diff --git a/src/hamster/widgets/tags.py b/src/hamster/widgets/tags.py
index cbb5938..224b005 100644
--- a/src/hamster/widgets/tags.py
+++ b/src/hamster/widgets/tags.py
@@ -58,9 +58,18 @@ class TagsEntry(gtk.Entry):
 
         self._parent_click_watcher = None # bit lame but works
 
-        runtime.storage.connect('tags-changed', self.refresh_tags)
+        self.external_listeners = [
+            (runtime.storage, runtime.storage.connect('tags-changed', self.refresh_tags))
+        ]
         self.show()
         self.populate_suggestions()
+        self.connect("destroy", self.on_destroy)
+
+    def on_destroy(self, window):
+        for obj, handler in self.external_listeners:
+            obj.disconnect(handler)
+        self.popup.destroy()
+        self.popup = None
 
 
     def refresh_tags(self, event):
diff --git a/src/hamster/widgets/timeinput.py b/src/hamster/widgets/timeinput.py
index c931205..c1434e9 100644
--- a/src/hamster/widgets/timeinput.py
+++ b/src/hamster/widgets/timeinput.py
@@ -65,7 +65,11 @@ class TimeInput(gtk.Entry):
 
         self.connect("changed", self._on_text_changed)
         self.show()
+        self.connect("destroy", self.on_destroy)
 
+    def on_destroy(self, window):
+        self.popup.destroy()
+        self.popup = None
 
     def set_time(self, time):
         time = time or dt.time()



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]