Skip to content

Commit

Permalink
[GUI] show active window after minimizing (#142)
Browse files Browse the repository at this point in the history
* cleanup and comment

* move fct back to base

* move test

* removed unused imports

* missing

* first working version of working minimize

* fixing call order

* cleanup

* minor

* cleanup and adding tests

* adding another test

* adding description

* fixing issues

* adding tests

* missing import
  • Loading branch information
philbucher authored Aug 18, 2020
1 parent f4eb1f0 commit 3c40af0
Show file tree
Hide file tree
Showing 12 changed files with 174 additions and 52 deletions.
18 changes: 18 additions & 0 deletions kratos_salome_plugin/gui/active_window.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# _ __ _ ___ _ ___ _ _
# | |/ /_ _ __ _| |_ ___ __/ __| __ _| |___ _ __ ___| _ \ |_ _ __ _(_)_ _
# | ' <| '_/ _` | _/ _ (_-<__ \/ _` | / _ \ ' \/ -_) _/ | || / _` | | ' \
# |_|\_\_| \__,_|\__\___/__/___/\__,_|_\___/_|_|_\___|_| |_|\_,_\__, |_|_||_|
# |___/
# License: BSD License ; see LICENSE
#
# Main authors: Philipp Bucher (https://github.com/philbucher)
#

"""
The currently active window is saved in a global variable
This is necessary in case the window gets minimized
I.e. when the plugin is reopened (with a previously minimized window)
then the last opened window is shown again instead of creating again the base window
"""

ACTIVE_WINDOW=None
28 changes: 25 additions & 3 deletions kratos_salome_plugin/gui/base_window.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,22 @@

# qt imports
from PyQt5.QtWidgets import QMainWindow
from PyQt5.QtCore import Qt, QTimer
from PyQt5.QtCore import Qt, QTimer, QEvent
from PyQt5.QtGui import QIcon
from PyQt5 import uic

# plugin imports
import kratos_salome_plugin.gui.active_window as active_window
from kratos_salome_plugin.utilities import GetAbsPathInPlugin
from kratos_salome_plugin.utilities import PathCheck


class BaseWindow(QMainWindow):
def __init__(self, ui_form_path, parent=None):
logger.debug('Creating BaseWindow')

super().__init__()

logger.debug('Creating %s', self.__class__.__name__)

PathCheck(ui_form_path)
self.__InitUI(ui_form_path)

Expand All @@ -39,6 +40,14 @@ def __init__(self, ui_form_path, parent=None):
if self.parent:
self.parent.hide()

def ShowOnTop(self) -> None:
"""show and activate the window, works both if opened newly or minimized
see https://kb.froglogic.com/squish/qt/howto/maximizing-minimizing-restoring-resizing-positioning-windows/
"""
self.show()
self.activateWindow()
self.setWindowState(Qt.WindowNoState)

def StatusBarInfo(self, message: str, msg_time: int=10) -> None:
"""show an info message in the statusbar
input for time is in seconds
Expand All @@ -64,8 +73,21 @@ def closeEvent(self, event):
if self.parent:
self.parent.show()

# resetting the global var to not accidentially keep a closed window alive
# this could happen if the window was minimized at some point
active_window.ACTIVE_WINDOW = None

super().closeEvent(event)

def changeEvent(self, event):
if event.type() == QEvent.WindowStateChange:
if self.windowState() & Qt.WindowMinimized:
# saving the currently active window such that it can be maximized again
# when the plugin is re-opened in salome
active_window.ACTIVE_WINDOW = self

super().changeEvent(event)

def __InitUI(self, ui_form_path) -> None:
"""initialize the user interface from the "ui" file
also set some settings that cannot be specified through the "ui" file
Expand Down
1 change: 0 additions & 1 deletion kratos_salome_plugin/gui/groups_window.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@

class GroupsWindow(BaseWindow):
def __init__(self, parent):
logger.debug('Creating GroupsWindow')
super().__init__(Path(GetAbsPathInPlugin("gui", "ui_forms", "groups_window.ui")), parent)


Expand Down
8 changes: 3 additions & 5 deletions kratos_salome_plugin/gui/plugin_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
from kratos_salome_plugin.exceptions import UserInputError
from kratos_salome_plugin.gui.plugin_main_window import PluginMainWindow
from kratos_salome_plugin.gui.about import ShowAbout
import kratos_salome_plugin.gui.active_window as active_window
from kratos_salome_plugin.gui.project_manager import ProjectManager
from kratos_salome_plugin.gui.project_path_handler import ProjectPathHandler

Expand All @@ -35,22 +36,19 @@ class PluginController:
def __init__(self):
logger.debug('Creating PluginController')
self._main_window = PluginMainWindow()
active_window.ACTIVE_WINDOW = self._main_window

self.__InitializeMembers()

self.__ConnectMainWindow()

def ShowMainWindow(self) -> None:
"""show main window"""
self._main_window.ShowOnTop()


def __InitializeMembers(self) -> None:
"""completely reinitialize members to clean them"""
self._project_manager = ProjectManager()
self._project_path_handler = ProjectPathHandler()
self._previous_save_path = None


def __ConnectMainWindow(self) -> None:
### File menu
self._main_window.actionNew.triggered.connect(self._New)
Expand Down
17 changes: 5 additions & 12 deletions kratos_salome_plugin/gui/plugin_main_window.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,31 +16,24 @@
import logging
logger = logging.getLogger(__name__)

# qt imports
from PyQt5.QtCore import Qt

# plugin imports
from kratos_salome_plugin.utilities import GetAbsPathInPlugin
from kratos_salome_plugin.gui.base_window import BaseWindow
import kratos_salome_plugin.gui.active_window as active_window


class PluginMainWindow(BaseWindow):
def __init__(self):
logger.debug('Creating PluginMainWindow')
super().__init__(Path(GetAbsPathInPlugin("gui", "ui_forms", "plugin_main_window.ui")))

def ShowOnTop(self) -> None:
"""show and activate the window, works both if opened newly or minimized
see https://kb.froglogic.com/squish/qt/howto/maximizing-minimizing-restoring-resizing-positioning-windows/
"""
self.show()
self.activateWindow()
self.setWindowState(Qt.WindowNoState)

def closeEvent(self, event):
"""prevent the window from closing, only hiding it
Note that this deliberately does not call the baseclass, as the event should be ignored
"""
# making this window the active one so that it can be reopened
# needed when reopening the plugin in salome
active_window.ACTIVE_WINDOW = self

event.ignore()
self.hide()

Expand Down
1 change: 1 addition & 0 deletions kratos_salome_plugin/reload_modules.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"base_application",
"gui.utilities",
"gui.about",
"gui.active_window",
"gui.project_path_handler",
"gui.project_manager",
"gui.base_window",
Expand Down
3 changes: 2 additions & 1 deletion salome_plugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ def InitializePlugin(context):

# plugin imports
from kratos_salome_plugin.gui.plugin_controller import PluginController
import kratos_salome_plugin.gui.active_window as active_window
import kratos_salome_plugin.version as plugin_version
from kratos_salome_plugin import salome_utilities
from kratos_salome_plugin.reload_modules import ReloadModules
Expand Down Expand Up @@ -71,7 +72,7 @@ def InitializePlugin(context):
# initialize only once the PluginController
PLUGIN_CONTROLLER = PluginController()

PLUGIN_CONTROLLER.ShowMainWindow()
active_window.ACTIVE_WINDOW.ShowOnTop()

logger.info("Successfully initialized plugin")

Expand Down
65 changes: 65 additions & 0 deletions tests/test_base_window.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

# plugin imports
from kratos_salome_plugin.gui.base_window import BaseWindow
import kratos_salome_plugin.gui.active_window as active_window

# tests imports
from testing_utilities import QtTestCase, GetTestsPath
Expand Down Expand Up @@ -59,6 +60,33 @@ def test_close_esc(self):
self.assertEqual(path_close_event.call_count, 1)


class TestBaseWindowWindowStates(QtTestCase):
"""This test makes sure the window shows up again after being minimized"""
def test_minimize(self):
window = BaseWindow(ui_file)
self.assertTrue(window.isHidden())

window.ShowOnTop()

window.setWindowState(Qt.WindowMinimized)

self.assertFalse(window.isActiveWindow())
self.assertTrue(window.isMinimized())
self.assertTrue(window.isVisible())
self.assertFalse(window.isHidden())
self.assertEqual(window.windowState(), Qt.WindowMinimized)

window.ShowOnTop()

# self.assertTrue(window.isActiveWindow()) # commented as doesn't work in the CI and in Linux, seems OS dependent
self.assertFalse(window.isMinimized())
self.assertTrue(window.isVisible())
self.assertFalse(window.isHidden())
self.assertEqual(window.windowState(), Qt.WindowNoState)

window.close()


class TestBaseWindowStatusBar(QtTestCase):
def test_StatusBarInfo(self):
window = BaseWindow(ui_file)
Expand Down Expand Up @@ -94,5 +122,42 @@ def test_hide_show_parent(self):
self.assertTrue(parent_window.isVisible())


class TestBaseWindowMinimize_ActiveWindow(QtTestCase):
def setUp(self):
# setting initial state
active_window.ACTIVE_WINDOW = None

def test_set_active_window(self):
window = BaseWindow(ui_file)
window.show()

window.setWindowState(Qt.WindowMinimized)

self.assertIs(active_window.ACTIVE_WINDOW, window)

def test_set_active_window_parent(self):
parent_window = BaseWindow(ui_file)
parent_window.show()

window = BaseWindow(ui_file, parent_window)

window.setWindowState(Qt.WindowMinimized)

self.assertIs(active_window.ACTIVE_WINDOW, window)

def test_set_active_window_reset(self):
parent_window = BaseWindow(ui_file)
parent_window.show()

window = BaseWindow(ui_file, parent_window)

window.setWindowState(Qt.WindowMinimized)

window.show()
window.close()

self.assertIsNone(active_window.ACTIVE_WINDOW) # make sure resettign the global var works


if __name__ == '__main__':
unittest.main()
17 changes: 14 additions & 3 deletions tests/test_plugin_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

# plugin imports
from kratos_salome_plugin.gui.plugin_controller import PluginController
import kratos_salome_plugin.gui.active_window as active_window

# tests imports
from testing_utilities import QtTestCase, CreateHDFStudyFile, DeleteDirectoryIfExisting, SalomeTestCaseWithBox, skipUnlessPythonVersionIsAtLeast
Expand Down Expand Up @@ -115,7 +116,7 @@ def test_help_website(self):
self.assertEqual(patch_fct.open.call_count, 1)


class TestPluginControllerWindowCloseReopen(QtTestCase):
class TestPluginControllerMainWindowCloseReopen(QtTestCase):
"""This test makes sure if the MainWindow is closed, it is not destroyed"""

def test_main_window_reopen(self):
Expand All @@ -125,11 +126,21 @@ def test_main_window_reopen(self):

controller._main_window.close()

controller.ShowMainWindow()
controller._main_window.ShowOnTop()

self.assertIs(orig_obj, controller._main_window)


class TestPluginControllerMainWindow_ActiveWindow(QtTestCase):
def test_main_window_active_window(self):
# setting initial state
active_window.ACTIVE_WINDOW = None

controller = PluginController()

self.assertIs(active_window.ACTIVE_WINDOW, controller._main_window)


# using a module local patch due to import of QFileDialog in project_path_handler
# see https://realpython.com/python-mock-library/#where-to-patch
_QFileDialog_patch = 'kratos_salome_plugin.gui.project_path_handler.QFileDialog.'
Expand All @@ -155,7 +166,7 @@ def test_New(self):

def test_Close(self):
controller = PluginController()
controller.ShowMainWindow()
controller._main_window.ShowOnTop()

self.assertFalse(controller._main_window.isMinimized())
self.assertTrue(controller._main_window.isVisible())
Expand Down
32 changes: 9 additions & 23 deletions tests/test_plugin_main_window.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

# plugin imports
from kratos_salome_plugin.gui.plugin_main_window import PluginMainWindow
import kratos_salome_plugin.gui.active_window as active_window

# tests imports
from testing_utilities import QtTestCase
Expand Down Expand Up @@ -117,31 +118,16 @@ def __CheckMockCalls(self, called_mock, exp_call_count=1):
self.assertFalse(self.mocks[mock_name].called, msg='Unexpected call for mock "{}": "{}"'.format(called_mock, mock_name))


class TestPluginMainWindowWindowStates(QtTestCase):
"""This test makes sure the window shows up again after being minimized"""
def test_minimize(self):
main_window = PluginMainWindow()
self.assertTrue(main_window.isHidden())
class TestPluginMainWindow_ActiveWindow(QtTestCase):
def test_set_active_window(self):
active_window.ACTIVE_WINDOW = None

main_window.ShowOnTop()
window = PluginMainWindow()
window.show()
window.close()

main_window.setWindowState(Qt.WindowMinimized)

self.assertFalse(main_window.isActiveWindow())
self.assertTrue(main_window.isMinimized())
self.assertTrue(main_window.isVisible())
self.assertFalse(main_window.isHidden())
self.assertEqual(main_window.windowState(), Qt.WindowMinimized)

main_window.ShowOnTop()

# self.assertTrue(main_window.isActiveWindow()) # commented as doesn't work in the CI and in Linux, seems OS dependent
self.assertFalse(main_window.isMinimized())
self.assertTrue(main_window.isVisible())
self.assertFalse(main_window.isHidden())
self.assertEqual(main_window.windowState(), Qt.WindowNoState)

main_window.close()
# make sure the main win is saved as active when closing it so that it can be reopened
self.assertIs(active_window.ACTIVE_WINDOW, window)


if __name__ == '__main__':
Expand Down
Loading

0 comments on commit 3c40af0

Please sign in to comment.