Repository: napari/napari

Issue:
Drop support for NAPARI_PERFMON with an existing QApplication
## 🧰 Task
I propose we drop support for using `NAPARI_PERFMON` with an existing `QApplication`.

The only real application for this is when you want to use something like `NAPARI_PERFMON=1 ipython --gui qt` and then start a napari viewer.   It then actually deletes the QApplication  so it can create a new one.  This is just super error prone and something we shouldn't try to support.  

Note: removing this doesn't prevent someone from using perfmon within ipython, one just has to create the napari viewer _before_ using the `%gui qt` magic... so we get to create the application ourselves.

Any oppositions to deleting the whole `convert_app_for_tracing` logic?
cc @sofroniewn


Discussion:
If we added "perfmon" to our settings now would we ever be able to enable perfmon without a restart or would we always require a restart even with this code? 

It might be nice to have perfmon in settings one day, and if we're ok always requiring a restart then i think it's fine to drop all that logic.

Would be nice to also update the perfmon docs to make it very clear how to support the ipython/ jupyter use case
> we ever be able to enable perfmon without a restart or would we always require a restart even with this code?

it would always require a restart.  Given the deep nature of the QApplication monkeypatching, it feels acceptable to me to enter in a "profiling session" explicitly.
> Would be nice to also update the perfmon docs to make it very clear how to support the ipython/ jupyter use case

the only thing we'd need to add is to _not_ use `%gui qt` before starting napari.  It's already not in the docs and gone from most of our examples... but it would also give a warning if someone tried it
> it would always require a restart. Given the deep nature of the QApplication monkeypatching, it feels acceptable to me to enter in a "profiling session" explicitly.

ok makes sense, i'm supportive!

> the only thing we'd need to add is to not use %gui qt before starting napari. It's already not in the docs and gone from most of our examples... but it would also give a warning if someone tried it

oh nice, even better!!

Resolving patch:
diff --git a/napari/_qt/perf/qt_event_tracing.py b/napari/_qt/perf/qt_event_tracing.py
--- a/napari/_qt/perf/qt_event_tracing.py
+++ b/napari/_qt/perf/qt_event_tracing.py
@@ -1,44 +1,16 @@
 """A special QApplication for perfmon that traces events.
 
-This file defines QApplicationWithTracing and convert_app_for_tracing(), both of
-which we use when perfmon is enabled to time Qt Events.
+This file defines QApplicationWithTracing which we use when perfmon is
+enabled to time Qt Events.
 
 When using perfmon there is a debug menu "Start Tracing" command as well as a
 dockable QtPerformance widget.
 """
-import sys
-
 from qtpy.QtCore import QEvent
 from qtpy.QtWidgets import QApplication, QWidget
 
 from ...utils import perf
 from ...utils.translations import trans
-from ..utils import delete_qapp
-
-
-def convert_app_for_tracing(app: QApplication) -> QApplication:
-    """If necessary replace existing app with our special tracing one.
-
-    Parameters
-    ----------
-    app : QApplication
-        The existing application if any.
-    """
-    if isinstance(app, QApplicationWithTracing):
-        # We're already using QApplicationWithTracing so there is nothing
-        # to do. This happens when napari is launched from the command
-        # line because we create a QApplicationWithTracing in get_app.
-        return app
-
-    if app is not None:
-
-        # We can't monkey patch QApplication.notify, since it's a SIP
-        # wrapped C++ method. So we delete the current app and create a new
-        # one. This must be done very early before any Qt objects are
-        # created or we will crash!
-        delete_qapp(app)
-
-    return QApplicationWithTracing(sys.argv)
 
 
 class QApplicationWithTracing(QApplication):
diff --git a/napari/_qt/qt_event_loop.py b/napari/_qt/qt_event_loop.py
--- a/napari/_qt/qt_event_loop.py
+++ b/napari/_qt/qt_event_loop.py
@@ -104,11 +104,6 @@ def get_app(
     Substitutes QApplicationWithTracing when the NAPARI_PERFMON env variable
     is set.
 
-    If the QApplication already exists, we call convert_app_for_tracing() which
-    deletes the QApplication and creates a new one. However here with get_app
-    we need to create the correct QApplication up front, or we will crash
-    because we'd be deleting the QApplication after we created QWidgets with
-    it, such as we do for the splash screen.
     """
     # napari defaults are all-or nothing.  If any of the keywords are used
     # then they are all used.
@@ -129,10 +124,13 @@ def get_app(
                 )
             )
         if perf_config and perf_config.trace_qt_events:
-            from .perf.qt_event_tracing import convert_app_for_tracing
+            warn(
+                trans._(
+                    "Using NAPARI_PERFMON with an already-running QtApp (--gui qt?) is not supported.",
+                    deferred=True,
+                )
+            )
 
-            # no-op if app is already a QApplicationWithTracing
-            app = convert_app_for_tracing(app)
     else:
         # automatically determine monitor DPI.
         # Note: this MUST be set before the QApplication is instantiated
diff --git a/napari/_qt/utils.py b/napari/_qt/utils.py
--- a/napari/_qt/utils.py
+++ b/napari/_qt/utils.py
@@ -7,7 +7,6 @@
 from qtpy.QtCore import QByteArray, QPropertyAnimation, QSize, Qt
 from qtpy.QtGui import QColor, QCursor, QDrag, QImage, QPainter, QPixmap
 from qtpy.QtWidgets import (
-    QApplication,
     QGraphicsColorizeEffect,
     QGraphicsOpacityEffect,
     QHBoxLayout,
@@ -303,23 +302,3 @@ def remove_flash_animation(widget: QWidget):
     """
     widget.setGraphicsEffect(None)
     del widget._flash_animation
-
-
-def delete_qapp(app):
-    """Delete a QApplication
-
-    Parameters
-    ----------
-    app : qtpy.QApplication
-    """
-    try:
-        # Pyside2
-        from shiboken2 import delete
-    except ImportError:
-        # PyQt5
-        from sip import delete
-
-    delete(app)
-    # calling a second time is necessary on PySide2...
-    # see: https://bugreports.qt.io/browse/PYSIDE-1470
-    QApplication.instance()


Test patch: