Repository: napari/napari

Issue:
Consider not raising when launching from IPython
## 🐛 Bug

When launching from Jupyter, it's common to put the entire preparation code (create the viewer, add an image, add labels, add points, ...) in the same cell, so raising by default is the natural choice. On the other hand, when I launch napari from IPython, typically I launch a Viewer, and still have quite a few statements to type before I want to actually use the viewer. In this situation, the new-ish "always raise" behaviour is very annoying. I'd like us to detect when we are launching from IPython and not raise in that situation.



Discussion:

Resolving patch:
diff --git a/napari/_qt/qt_main_window.py b/napari/_qt/qt_main_window.py
--- a/napari/_qt/qt_main_window.py
+++ b/napari/_qt/qt_main_window.py
@@ -22,6 +22,7 @@
 from ..resources import get_stylesheet
 from ..utils import perf
 from ..utils.io import imsave
+from ..utils.misc import in_jupyter
 from ..utils.theme import template
 from .dialogs.qt_about import QtAbout
 from .dialogs.qt_plugin_report import QtPluginErrReporter
@@ -428,17 +429,24 @@ def show(self):
         # Resize axis labels now that window is shown
         self.qt_viewer.dims._resize_axis_labels()
 
-        # We want to call Window._qt_window.raise_() in every case *except*
-        # when instantiating a viewer within a gui_qt() context for the
-        # _first_ time within the Qt app's lifecycle.
-        #
+        # We want to bring the viewer to the front when
+        # A) it is our own (gui_qt) event loop OR we are running in jupyter
+        # B) it is not the first time a QMainWindow is being created
+
         # `app_name` will be "napari" iff the application was instantiated in
         # gui_qt(). isActiveWindow() will be True if it is the second time a
-        # _qt_window has been created. See #732
+        # _qt_window has been created.
+        # See #721, #732, #735, #795, #1594
         app_name = QApplication.instance().applicationName()
-        if app_name != 'napari' or self._qt_window.isActiveWindow():
-            self._qt_window.raise_()  # for macOS
-            self._qt_window.activateWindow()  # for Windows
+        if (
+            app_name == 'napari' or in_jupyter()
+        ) and self._qt_window.isActiveWindow():
+            self.activate()
+
+    def activate(self):
+        """Make the viewer the currently active window."""
+        self._qt_window.raise_()  # for macOS
+        self._qt_window.activateWindow()  # for Windows
 
     def _update_palette(self, event=None):
         """Update widget color palette."""
diff --git a/napari/utils/misc.py b/napari/utils/misc.py
--- a/napari/utils/misc.py
+++ b/napari/utils/misc.py
@@ -27,6 +27,28 @@ def running_as_bundled_app() -> bool:
     return importlib_metadata.metadata("napari").get("App-ID") is not None
 
 
+def in_jupyter() -> bool:
+    """Return true if we're running in jupyter notebook/lab or qtconsole."""
+    try:
+        from IPython import get_ipython
+
+        return get_ipython().__class__.__name__ == 'ZMQInteractiveShell'
+    except Exception:
+        pass
+    return False
+
+
+def in_ipython() -> bool:
+    """Return true if we're running in an IPython interactive shell."""
+    try:
+        from IPython import get_ipython
+
+        return get_ipython().__class__.__name__ == 'TerminalInteractiveShell'
+    except Exception:
+        pass
+    return False
+
+
 def str_to_rgb(arg):
     """Convert an rgb string 'rgb(x,y,z)' to a list of ints [x,y,z].
     """


Test patch: