Repository: napari/napari

Issue:
window raise / menubar focus at conflict with each other
## 🐛 Bug

So turns out #721 which "raised" the main window as actually a reversion of #553 which had deleted that line to fix a menubar focus issue.

In #553 I made the following statement

> I'm not quite sure why the raise_ call was in there or if there are any negative issues of dropping it on other platforms, but I can confirm that on a mac everything still functions as expected, including usage from the notebook.

Turns out `raise_` was needed to bring the window to the front which is quite nice.

Now I'm not sure how to resolve both #553 and #721 at the same time!!!
@tlambert03 do you have any ideas?


Discussion:
oops, sorry!  (i'm assuming you meant #553?).  Yeah I know the issue you're referring to... and I've long since "grown used to it" with Qt (i've had the same thing for ages with LLSpy, and now I almost instinctively `cmd-tab` back and forth after starting).  I can say that it depends a bit on how the program is started... (but I can't remember at the moment _exactly_ how/why).  For instance, it didn't affect the `pyinstaller` app, and I even think when I started from a cli interface it was sometimes better?  For what it's worth, I've even noticed this sort of window/menu stealing at times when importing matplotlib with qt backend... so I think it's rather deep in Qt.

It's a real conundrum, and definitely something I'd love to get to the bottom of!
For now, I wouldn't mind if you wanted to revert #721 and leave this issue open to remember to think about it.  your call.
We don't need to revert right away, i might play around with it a bit more now that I know why `raise_` is called, but we might want to change before the next release.

Personally I find it less confusing if I can't see the window from the notebook than if the drop downs don't work first time, but hopefully we don't have to make that trade off!!
I just spent some timing working on this, but no progress!! I can only get either / or to work not both!!
Tried again, still no luck - if any Qt / Mac experts out there have any ideas on how to resolve this it would be much appreciated, I'm still making no progress
gonna try to see what I can figure out here...  One quick observation that might be a clue:
with `self._qt_window.raise_()` in the `Window.show()` method (as it currently is in `master`), I only get the inactive menubar problem if I launch the viewer within the `gui_qt()` context manager.  If I launch it as `__main__` using `python -m napari`, then the menubar is immediately active. 

If I launch ipython with a Qt event loop (`ipython --gui=qt`) then calling `napari.view_image` _also_ yields a viewer with an active menubar.  However, when launching ipython like that, my terminal actually loses focus and the "current app" in the osx menubar is "python", forcing me to click on the terminal window to actually continue with launching the viewer.  I think it's that momentary loss of focus that "takes care" of the problem.

so it seems to be only when starting the qt event loop and then immediately raising the main_window when the menubar is inactive?  @sofroniewn, can you confirm that you're using `with napari.gui_qt()` to start your viewer when you have the inactive menus problem?
Yes that is the case, which means a potential solution might just be to set up some logic / extra keyword params to avoid the ‘raise_’ call when launching with our ‘gui_qt’ event loop and have it called by default from IPython and Jupyter - where it is really needed
yeah, I'm working on something like that now.  I noticed that the `raise_` call is only necessary on the _second_ call to `gui_qt()` (otherwise the viewer is already on top), and during that second call to gui_qt(), `Qapplication.instance()` returns a valid instance, whereas in the first call it returns `None`.  So I'm going to see if I can leverage that somehow

Resolving patch:
diff --git a/napari/_qt/event_loop.py b/napari/_qt/event_loop.py
--- a/napari/_qt/event_loop.py
+++ b/napari/_qt/event_loop.py
@@ -22,7 +22,12 @@ def gui_qt(*, startup_logo=False):
     IPython with the Qt GUI event loop enabled by default by using
     ``ipython --gui=qt``.
     """
-    app = QApplication.instance() or QApplication(sys.argv)
+    app = QApplication.instance()
+    if not app:
+        # if this is the first time the Qt app is being instantiated, we set
+        # the name, so that we know whether to raise_ in Window.show()
+        app = QApplication(sys.argv)
+        app.setApplicationName('napari')
     if startup_logo:
         logopath = join(dirname(__file__), '..', 'resources', 'logo.png')
         splash_widget = QSplashScreen(QPixmap(logopath).scaled(400, 400))
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
@@ -27,6 +27,7 @@
     QAction,
     QShortcut,
     QStatusBar,
+    QApplication,
 )
 from qtpy.QtCore import Qt  # noqa: E402
 from qtpy.QtGui import QKeySequence  # noqa: E402
@@ -270,12 +271,21 @@ def resize(self, width, height):
         self._qt_window.resize(width, height)
 
     def show(self):
-        """Resize, show, and bring forward the window.
-        """
+        """Resize, show, and bring forward the window."""
         self._qt_window.resize(self._qt_window.layout().sizeHint())
         self._qt_window.show()
-        # make sure window is not hidden, e.g. by browser window in Jupyter
-        self._qt_window.raise_()
+
+        # 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.
+        #
+        # `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
+        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
 
     def _update_palette(self, palette):
         # set window styles which don't use the primary stylesheet


Test patch: