diff --git a/gui/wxpython/animation/frame.py b/gui/wxpython/animation/frame.py index 4db0955b942..c6bb476705d 100644 --- a/gui/wxpython/animation/frame.py +++ b/gui/wxpython/animation/frame.py @@ -354,9 +354,10 @@ def OnHelp(self, event): RunCommand("g.manual", quiet=True, entry="wxGUI.animation") def OnCloseWindow(self, event): - if self.controller.timer.IsRunning(): - self.controller.timer.Stop() - CleanUp(TMP_DIR)() + self.close(event) + + def close(self, event=None): + self.__del__() # noqa: PLC2801, C2801 self._mgr.UnInit() self.Destroy() @@ -366,6 +367,7 @@ def __del__(self): if self.controller.timer.IsRunning(): self.controller.timer.Stop() CleanUp(TMP_DIR)() + tgis.stop_subprocesses() class AnimationsPanel(wx.Panel): diff --git a/gui/wxpython/lmgr/frame.py b/gui/wxpython/lmgr/frame.py index 22714ee01fe..647288edb37 100644 --- a/gui/wxpython/lmgr/frame.py +++ b/gui/wxpython/lmgr/frame.py @@ -123,6 +123,9 @@ def __init__( self._giface = LayerManagerGrassInterface(self) + # List of wxGUI tools frames with initilized tgis framework suprocesses + self.framesWithInitTgisSubprocesses = [] + # workspace manager self.workspace_manager = WorkspaceManager(lmgr=self, giface=self._giface) self._setTitle() @@ -1698,6 +1701,7 @@ def OnAnimationTool(self, event=None, cmd=None): frame = AnimationFrame(parent=self, giface=self._giface) frame.CentreOnScreen() frame.Show() + self.framesWithInitTgisSubprocesses.append(frame) tree = self.GetLayerTree() if tree: @@ -1725,8 +1729,9 @@ def OnTimelineTool(self, event=None, cmd=None): except ImportError: GError(parent=self, message=_("Unable to start Timeline Tool.")) return - frame = TimelineFrame(None) + frame = TimelineFrame(parent=self) frame.Show() + self.framesWithInitTgisSubprocesses.append(frame) def OnTplotTool(self, event=None, cmd=None): """Launch Temporal Plot Tool""" @@ -1737,6 +1742,7 @@ def OnTplotTool(self, event=None, cmd=None): return frame = TplotFrame(parent=self, giface=self._giface) frame.Show() + self.framesWithInitTgisSubprocesses.append(frame) def OnHistogram(self, event): """Init histogram display canvas and tools""" @@ -2281,6 +2287,7 @@ def OnCloseWindowOrExit(self, event): if ret != wx.ID_CANCEL: self._closeWindow(event) if ret == wx.ID_YES: + self._stopFramesWithInitTgisSubprocesses() self._quitGRASS() def _closeWindow(self, event): @@ -2301,6 +2308,23 @@ def _closeWindow(self, event): self._auimgr.UnInit() self.Destroy() + def _stopFramesWithInitTgisSubprocesses(self): + """Stop wxGUI tools frames initiallized tgis framework + subprocesses + + All these wxGUI Animation, Timeline, Temporal tool initialize tgis + framework (messenger and C-interface subprocesses) which require to be + properly stopped before quitting GRASS (sending SIGTERM signal), because + it causes "freeze" wxGUI tool window and wxGUI processes are not terminated. + """ + try: + for frame in self.framesWithInitTgisSubprocesses: + if frame: + frame.close() + # OSError: handle is closed error + except OSError: + pass + def _quitGRASS(self): """Quit GRASS terminal""" shellPid = get_shell_pid() diff --git a/gui/wxpython/main_window/frame.py b/gui/wxpython/main_window/frame.py index 84ad713ffa5..f20b31061fb 100644 --- a/gui/wxpython/main_window/frame.py +++ b/gui/wxpython/main_window/frame.py @@ -141,6 +141,9 @@ def __init__( self._giface = LayerManagerGrassInterface(self) + # List of wxGUI tools frames with initilized tgis framework suprocesses + self.framesWithInitTgisSubprocesses = [] + # workspace manager self.workspace_manager = WorkspaceManager(lmgr=self, giface=self._giface) self._setTitle() @@ -1854,6 +1857,7 @@ def OnAnimationTool(self, event=None, cmd=None): frame = AnimationFrame(parent=self, giface=self._giface) frame.CentreOnScreen() frame.Show() + self.framesWithInitTgisSubprocesses.append(frame) tree = self.GetLayerTree() if tree: @@ -1881,8 +1885,9 @@ def OnTimelineTool(self, event=None, cmd=None): except ImportError: GError(parent=self, message=_("Unable to start Timeline Tool.")) return - frame = TimelineFrame(None) + frame = TimelineFrame(parent=self) frame.Show() + self.framesWithInitTgisSubprocesses.append(frame) def OnTplotTool(self, event=None, cmd=None): """Launch Temporal Plot Tool""" @@ -1893,6 +1898,7 @@ def OnTplotTool(self, event=None, cmd=None): return frame = TplotFrame(parent=self, giface=self._giface) frame.Show() + self.framesWithInitTgisSubprocesses.append(frame) def OnHistogram(self, event): """Init histogram display canvas and tools""" @@ -2390,6 +2396,7 @@ def OnCloseWindowOrExit(self, event): if ret != wx.ID_CANCEL: self._closeWindow(event) if ret == wx.ID_YES: + self._stopFramesWithInitTgisSubprocesses() self._quitGRASS() def _closeWindow(self, event): @@ -2410,6 +2417,23 @@ def _closeWindow(self, event): self._auimgr.UnInit() self.Destroy() + def _stopFramesWithInitTgisSubprocesses(self): + """Stop wxGUI tools frames initiallized tgis framework + subprocesses + + All these wxGUI Animation, Timeline, Temporal tool initialize tgis + framework (messenger and C-interface subprocesses) which require to be + properly stopped before quitting GRASS (sending SIGTERM signal), because + it causes "freeze" wxGUI tool window and wxGUI processes are not terminated. + """ + try: + for frame in self.framesWithInitTgisSubprocesses: + if frame: + frame.close() + # OSError: handle is closed error + except OSError: + pass + def _quitGRASS(self): """Quit GRASS terminal""" shellPid = get_shell_pid() diff --git a/gui/wxpython/timeline/frame.py b/gui/wxpython/timeline/frame.py index e1004ce35d4..3dc5b1b41aa 100644 --- a/gui/wxpython/timeline/frame.py +++ b/gui/wxpython/timeline/frame.py @@ -88,6 +88,9 @@ def __init__(self, parent, title=_("Timeline Tool")): self.Bind(wx.EVT_CLOSE, self.OnClose) def OnClose(self, event): + self.close(event) + + def close(self, event=None): """Close the database interface and stop the messenger and C-interface subprocesses. """ diff --git a/gui/wxpython/tplot/frame.py b/gui/wxpython/tplot/frame.py index adaed601ebc..9b9fea523f9 100755 --- a/gui/wxpython/tplot/frame.py +++ b/gui/wxpython/tplot/frame.py @@ -140,6 +140,9 @@ def __del__(self): tgis.stop_subprocesses() def onClose(self, evt): + self.close(evt) + + def close(self, evt=None): if self._giface.GetMapDisplay(): self.coorval.OnClose() self.cats.OnClose()