diff --git a/python/app/dialog.py b/python/app/dialog.py index f37fa55a..5113a589 100644 --- a/python/app/dialog.py +++ b/python/app/dialog.py @@ -38,6 +38,7 @@ from .note_updater import NoteUpdater from .widget_all_fields import AllFieldsWidget from .work_area_dialog import WorkAreaDialog +from .utils import monitor_qobject_lifetime shotgun_model = sgtk.platform.import_framework( "tk-framework-shotgunutils", "shotgun_model" @@ -134,9 +135,9 @@ def __init__(self, parent=None): self._action_manager.refresh_request.connect(self.refresh) # create a background task manager - self._task_manager = task_manager.BackgroundTaskManager( - self, start_processing=True, max_threads=2 - ) + self._task_manager = task_manager.BackgroundTaskManager(self, max_threads=2) + monitor_qobject_lifetime(self._task_manager, "Main task manager") + self._task_manager.start_processing() # register the data fetcher with the global schema manager shotgun_globals.register_bg_task_manager(self._task_manager) @@ -258,7 +259,6 @@ def closeEvent(self, event): self._overlay.repaint() try: - # register the data fetcher with the global schema manager shotgun_globals.unregister_bg_task_manager(self._task_manager) @@ -278,6 +278,10 @@ def closeEvent(self, event): # close splash self._overlay.hide() + # destroy the current Qt Model + self._overlay.destroy() + self.destroy() + # okay to close dialog event.accept() @@ -834,7 +838,6 @@ def _change_work_area(self, entity_type, entity_id): res = dialog.exec_() if res == QtGui.QDialog.Accepted: - if dialog.is_new_task: # user wants to create a new task @@ -869,7 +872,7 @@ def _change_work_area(self, entity_type, entity_id): } self._app.log_debug( - "Creating new task:\n%s" % pprint.pprint(sg_data) + "Creating new task:\n%s" % pprint.pformat(sg_data) ) task_data = self._app.shotgun.create("Task", sg_data) (entity_type, entity_id) = (task_data["type"], task_data["id"]) @@ -1208,6 +1211,7 @@ def setup_entity_model_view(self, entity_data): entity_data["model"] = ModelClass( entity_data["entity_type"], entity_data["view"], self._task_manager ) + monitor_qobject_lifetime(entity_data["model"]) # create proxy for sorting entity_data["sort_proxy"] = FilterItemProxyModel(self) @@ -1289,7 +1293,6 @@ def _sort_menu_setup(self, task_tab_data): self._sort_menu_actions() def _field_filters(self): - # ---- define a few simple filter methods for use by the menu def field_filter(field): diff --git a/python/app/utils.py b/python/app/utils.py index ab6c53db..7f935f26 100644 --- a/python/app/utils.py +++ b/python/app/utils.py @@ -216,3 +216,65 @@ def create_human_readable_timestamp(datetime_obj): time_str = datetime_obj.strftime("%H:%M") return (time_str, full_time_str) + + +# storage for any tracked qobjects +_g_monitored_qobjects = {} + + +def monitor_qobject_lifetime(obj, name=""): + """ + Debug method to help track the lifetime of a QObject derived instance. Hooks into + the instances destroyed signal to report when the QObject has been destroyed. + + :param obj: The QObject instance to monitor + :param name: An optional name to be appended to the debug output, useful for identifying + a specific instance of a class. + """ + msg = type(obj).__name__ + if name: + msg = "%s [%s]" % (msg, name) + + app = sgtk.platform.current_bundle() + + global _g_monitored_qobjects + uid = len(_g_monitored_qobjects) + _g_monitored_qobjects[uid] = msg + obj.destroyed.connect(lambda m=msg, u=uid: _on_qobject_destroyed(m, u)) + + +def _on_qobject_destroyed(name, uid): + """ + Slot triggered whenever a monitored qobject is destroyed - reports to debug that the object + was destroyed. + + :param name: Name of the instance that was destroyed + :param uid: Unique id of the QObject used to look it up in the monitored list + """ + app = sgtk.platform.current_bundle() + app.log_debug("%s destroyed" % name) + global _g_monitored_qobjects + try: + del _g_monitored_qobjects[uid] + except KeyError: + pass + + +def report_non_destroyed_qobjects(clear_list=True): + """ + Report any monitored QObjects that have not yet been destroyed. Care should be taken to + account for QObjects that are pending destruction via deleteLater signals that may be + pending. + + :param clear_list: If true then the list of monitored QObjects will be cleared after + this function has reported them. + """ + app = sgtk.platform.current_bundle() + global _g_monitored_qobjects + app.log_debug( + "%d monitored QObjects have not been destroyed!" % len(_g_monitored_qobjects) + ) + for msg in _g_monitored_qobjects.values(): + app.log_debug(" - %s" % msg) + if clear_list: + _g_monitored_qobjects.clear() diff --git a/python/app/widget_all_fields.py b/python/app/widget_all_fields.py index 0ed6ab90..3aa9f69a 100644 --- a/python/app/widget_all_fields.py +++ b/python/app/widget_all_fields.py @@ -85,7 +85,7 @@ def clear(self): # set it's parent to None so that it is removed from the widget hierarchy x.setParent(None) # mark it to be deleted when event processing returns to the main loop - x.deleteLater() + x.destroy() self._widgets = []