← Back to team overview

openlp-core team mailing list archive

[Merge] lp:~springermac/openlp/menubar-fix into lp:openlp

 

Jonathan Springer has proposed merging lp:~springermac/openlp/menubar-fix into lp:openlp.

Requested reviews:
  OpenLP Core (openlp-core)

For more details, see:
https://code.launchpad.net/~springermac/openlp/menubar-fix/+merge/281345

Make sure the main display on Mac OS X stays above the menu bar and dock but still allow the main window to be focused
-- 
Your team OpenLP Core is requested to review the proposed merge of lp:~springermac/openlp/menubar-fix into lp:openlp.
=== modified file 'openlp/core/ui/generaltab.py'
--- openlp/core/ui/generaltab.py	2015-11-07 00:49:40 +0000
+++ openlp/core/ui/generaltab.py	2015-12-24 19:52:15 +0000
@@ -317,6 +317,8 @@
             self.custom_Y_value_edit.value(),
             self.custom_width_value_edit.value(),
             self.custom_height_value_edit.value())
+        self.screens.override['number'] = self.screens.which_screen(self.screens.override['size'])
+        self.screens.override['primary'] = (self.screens.desktop.primaryScreen() == self.screens.override['number'])
         if self.override_radio_button.isChecked():
             self.screens.set_override_display()
         else:

=== modified file 'openlp/core/ui/maindisplay.py'
--- openlp/core/ui/maindisplay.py	2015-12-14 00:21:58 +0000
+++ openlp/core/ui/maindisplay.py	2015-12-24 19:52:15 +0000
@@ -39,6 +39,13 @@
 from openlp.core.lib.theme import BackgroundType
 from openlp.core.ui import HideMode, AlertLocation
 
+if is_macosx():
+    from ctypes import pythonapi, c_void_p, c_char_p, py_object
+
+    from sip import voidptr
+    from objc import objc_object
+    from AppKit import NSMainMenuWindowLevel, NSWindowCollectionBehaviorManaged
+
 log = logging.getLogger(__name__)
 
 OPAQUE_STYLESHEET = """
@@ -154,15 +161,30 @@
         # regressions on other platforms.
         if is_macosx():
             window_flags = QtCore.Qt.FramelessWindowHint | QtCore.Qt.Window
-            # For primary screen ensure it stays above the OS X dock
-            # and menu bar
-            if self.screens.current['primary']:
-                self.setWindowState(QtCore.Qt.WindowFullScreen)
-            else:
-                window_flags |= QtCore.Qt.WindowStaysOnTopHint
         self.setWindowFlags(window_flags)
         self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
         self.set_transparency(False)
+        if is_macosx():
+            if self.is_live:
+                # Get a pointer to the underlying NSView
+                try:
+                    nsview_pointer = self.winId().ascapsule()
+                except:
+                    nsview_pointer = voidptr(self.winId()).ascapsule()
+                # Set PyCapsule name so pyobjc will accept it
+                pythonapi.PyCapsule_SetName.restype = c_void_p
+                pythonapi.PyCapsule_SetName.argtypes = [py_object, c_char_p]
+                pythonapi.PyCapsule_SetName(nsview_pointer, c_char_p(b"objc.__object__"))
+                # Covert the NSView pointer into a pyobjc NSView object
+                self.pyobjc_nsview = objc_object(cobject=nsview_pointer)
+                # Set the window level so that the MainDisplay is above the menu bar and dock
+                self.pyobjc_nsview.window().setLevel_(NSMainMenuWindowLevel + 2)
+                # Set the collection behavior so the window is visible when Mission Control is activated
+                self.pyobjc_nsview.window().setCollectionBehavior_(NSWindowCollectionBehaviorManaged)
+                if self.screens.current['primary']:
+                    # Connect focusWindowChanged signal so we can change the window level when the display is not in
+                    # focus on the primary screen
+                    self.application.focusWindowChanged.connect(self.change_window_level)
         if self.is_live:
             Registry().register_function('live_display_hide', self.hide_display)
             Registry().register_function('live_display_show', self.show_display)
@@ -186,6 +208,12 @@
         Remove registered function on close.
         """
         if self.is_live:
+            if is_macosx():
+                # Block signals so signal we are disconnecting can't get called while we disconnect it
+                self.blockSignals(True)
+                if self.screens.current['primary']:
+                    self.application.focusWindowChanged.disconnect()
+                self.blockSignals(False)
             Registry().remove_function('live_display_hide', self.hide_display)
             Registry().remove_function('live_display_show', self.show_display)
             Registry().remove_function('update_display_css', self.css_changed)
@@ -500,6 +528,36 @@
             self.setCursor(QtCore.Qt.ArrowCursor)
             self.frame.evaluateJavaScript('document.body.style.cursor = "auto"')
 
+    def change_window_level(self, window):
+        """
+        Changes the display window level on Mac OS X so that the main window can be brought into focus but still allow
+        the main display to be above the menu bar and dock when it in focus.
+
+        :param window: Window from our application that focus changed to or None if outside our application
+        """
+        if is_macosx():
+            if window:
+                # Get different window ids' as int's
+                try:
+                    window_id = window.winId().__int__()
+                    main_window_id = self.main_window.winId().__int__()
+                    self_id = self.winId().__int__()
+                except:
+                    return
+                # If the passed window has the same id as our window make sure the display has the proper level and
+                # collection behavior.
+                if window_id == self_id:
+                    self.pyobjc_nsview.window().setLevel_(NSMainMenuWindowLevel + 2)
+                    self.pyobjc_nsview.window().setCollectionBehavior_(NSWindowCollectionBehaviorManaged)
+                # Else set the displays window level back to normal since we are trying to focus a window other than
+                # the display.
+                else:
+                    self.pyobjc_nsview.window().setLevel_(0)
+                    self.pyobjc_nsview.window().setCollectionBehavior_(NSWindowCollectionBehaviorManaged)
+                    # If we are trying to focus the main window raise it now to complete the focus change.
+                    if window_id == main_window_id:
+                        self.main_window.raise_()
+
 
 class AudioPlayer(OpenLPMixin, QtCore.QObject):
     """

=== modified file 'scripts/check_dependencies.py'
--- scripts/check_dependencies.py	2015-12-21 00:43:38 +0000
+++ scripts/check_dependencies.py	2015-12-24 19:52:15 +0000
@@ -41,6 +41,7 @@
 
 IS_WIN = sys.platform.startswith('win')
 IS_LIN = sys.platform.startswith('lin')
+IS_MAC = sys.platform.startswith('dar')
 
 
 VERS = {
@@ -66,6 +67,11 @@
     'dbus',
 ]
 
+MACOSX_MODULES = [
+    'objc',
+    'AppKit'
+]
+
 
 MODULES = [
     'PyQt5',
@@ -234,6 +240,10 @@
         print('Checking for Linux specific modules...')
         for m in LINUX_MODULES:
             check_module(m)
+    elif IS_MAC:
+        print('Checking for Mac OS X specific modules...')
+        for m in MACOSX_MODULES:
+            check_module(m)
     verify_versions()
     print_qt_image_formats()
     print_enchant_backends_and_languages()

=== modified file 'tests/functional/openlp_core_ui/test_maindisplay.py'
--- tests/functional/openlp_core_ui/test_maindisplay.py	2015-11-07 00:49:40 +0000
+++ tests/functional/openlp_core_ui/test_maindisplay.py	2015-12-24 19:52:15 +0000
@@ -26,7 +26,7 @@
 
 from PyQt5 import QtCore
 
-from openlp.core.common import Registry
+from openlp.core.common import Registry, is_macosx
 from openlp.core.lib import ScreenList
 from openlp.core.ui import MainDisplay
 from openlp.core.ui.maindisplay import TRANSPARENT_STYLESHEET, OPAQUE_STYLESHEET
@@ -34,6 +34,13 @@
 from tests.helpers.testmixin import TestMixin
 from tests.functional import MagicMock, patch
 
+if is_macosx():
+    from ctypes import pythonapi, c_void_p, c_char_p, py_object
+
+    from sip import voidptr
+    from objc import objc_object
+    from AppKit import NSMainMenuWindowLevel, NSWindowCollectionBehaviorManaged
+
 
 class TestMainDisplay(TestCase, TestMixin):
 
@@ -135,40 +142,46 @@
         mocked_bibles_plugin.refresh_css.assert_called_with(main_display.frame)
 
     @patch('openlp.core.ui.maindisplay.is_macosx')
-    def macosx_non_primary_screen_window_flags_state_test(self, is_macosx):
-        """
-        Test that on Mac OS X when the current screen isn't primary we set the proper window flags and window state
-        """
-        # GIVEN: A new SlideController instance on Mac OS X with the current display not being primary.
+    def macosx_display_window_flags_state_test(self, is_macosx):
+        """
+        Test that on Mac OS X we set the proper window flags
+        """
+        # GIVEN: A new SlideController instance on Mac OS X.
         is_macosx.return_value = True
-        self.screens.set_current_display(1)
+        self.screens.set_current_display(0)
         display = MagicMock()
 
         # WHEN: The default controller is built.
         main_display = MainDisplay(display)
 
-        # THEN: The window flags and state should be the same as those needed on Mac OS X for the non primary display.
-        self.assertEqual(QtCore.Qt.WindowStaysOnTopHint | QtCore.Qt.Window | QtCore.Qt.FramelessWindowHint,
+        # THEN: The window flags should be the same as those needed on Mac OS X.
+        self.assertEqual(QtCore.Qt.Window | QtCore.Qt.FramelessWindowHint,
                          main_display.windowFlags(),
-                         'The window flags should be Qt.WindowStaysOnTop, Qt.Window, and Qt.FramelessWindowHint.')
-        self.assertNotEqual(QtCore.Qt.WindowFullScreen, main_display.windowState(),
-                            'The window state should not be full screen.')
+                         'The window flags should be Qt.Window, and Qt.FramelessWindowHint.')
 
-    @patch('openlp.core.ui.maindisplay.is_macosx')
-    def macosx_primary_screen_window_flags_state_test(self, is_macosx):
-        """
-        Test that on Mac OS X when the current screen is primary we set the proper window flags and window state
-        """
-        # GIVEN: A new SlideController instance on Mac OS X with the current display being primary.
-        is_macosx.return_value = True
+    def macosx_display_test(self):
+        """
+        Test display on Mac OS X
+        """
+        if not is_macosx():
+            self.skipTest('Can only run test on Mac OS X due to pyobjc dependency.')
+        # GIVEN: A new SlideController instance on Mac OS X.
         self.screens.set_current_display(0)
         display = MagicMock()
 
-        # WHEN: The default controller is built.
+        # WHEN: The default controller is built and a reference to the underlying NSView is stored.
         main_display = MainDisplay(display)
+        try:
+            nsview_pointer = main_display.winId().ascapsule()
+        except:
+            nsview_pointer = voidptr(main_display.winId()).ascapsule()
+        pythonapi.PyCapsule_SetName.restype = c_void_p
+        pythonapi.PyCapsule_SetName.argtypes = [py_object, c_char_p]
+        pythonapi.PyCapsule_SetName(nsview_pointer, c_char_p(b"objc.__object__"))
+        pyobjc_nsview = objc_object(cobject=nsview_pointer)
 
-        # THEN: The window flags and state should be the same as those needed on Mac OS X for the primary display.
-        self.assertEqual(QtCore.Qt.Window | QtCore.Qt.FramelessWindowHint, main_display.windowFlags(),
-                         'The window flags should be Qt.Window and Qt.FramelessWindowHint.')
-        self.assertEqual(QtCore.Qt.WindowFullScreen, main_display.windowState(),
-                         'The window state should be full screen.')
+        # THEN: The window level and collection behavior should be the same as those needed for Mac OS X.
+        self.assertEqual(pyobjc_nsview.window().level(), NSMainMenuWindowLevel + 2,
+                         'Window level should be NSMainMenuWindowLevel + 2')
+        self.assertEqual(pyobjc_nsview.window().collectionBehavior(), NSWindowCollectionBehaviorManaged,
+                         'Window collection behavior should be NSWindowCollectionBehaviorManaged')


Follow ups