Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Shaperilio/qtgui fixes #13957

Merged
merged 12 commits into from
Mar 13, 2023
5 changes: 5 additions & 0 deletions IPython/external/qt_loaders.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"""
import importlib.abc
import sys
import os
import types
from functools import partial, lru_cache
import operator
Expand Down Expand Up @@ -368,6 +369,10 @@ def load_qt(api_options):
commit_api(api)
return result
else:
# Clear the environment variable since it doesn't work.
if "QT_API" in os.environ:
del os.environ["QT_API"]

raise ImportError(
"""
Could not load requested Qt binding. Please ensure that
Expand Down
16 changes: 14 additions & 2 deletions IPython/terminal/interactiveshell.py
Original file line number Diff line number Diff line change
Expand Up @@ -913,10 +913,19 @@ def inputhook(self, context):

active_eventloop = None
def enable_gui(self, gui=None):
if self._inputhook is None and gui is None:
print("No event loop hook running.")
return

if self._inputhook is not None and gui is not None:
warn(
f"Shell was already running a gui event loop for {self.active_eventloop}; switching to {gui}."
print(
f"Shell is already running a gui event loop for {self.active_eventloop}. "
"Call with no arguments to disable the current loop."
)
return
if self._inputhook is not None and gui is None:
self.active_eventloop = self._inputhook = None

if gui and (gui not in {"inline", "webagg"}):
# This hook runs with each cycle of the `prompt_toolkit`'s event loop.
self.active_eventloop, self._inputhook = get_inputhook_name_and_func(gui)
Expand All @@ -934,15 +943,18 @@ def enable_gui(self, gui=None):
# same event loop as the rest of the code. don't use an actual
# input hook. (Asyncio is not made for nesting event loops.)
self.pt_loop = get_asyncio_loop()
print("Installed asyncio event loop hook.")

elif self._inputhook:
# If an inputhook was set, create a new asyncio event loop with
# this inputhook for the prompt.
self.pt_loop = new_eventloop_with_inputhook(self._inputhook)
print(f"Installed {self.active_eventloop} event loop hook.")
else:
# When there's no inputhook, run the prompt in a separate
# asyncio event loop.
self.pt_loop = asyncio.new_event_loop()
print("GUI event loop hook disabled.")

# Run !system commands directly, not through pipes, so terminal programs
# work correctly.
Expand Down
12 changes: 9 additions & 3 deletions IPython/terminal/pt_inputhooks/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,16 +69,17 @@ def set_qt_api(gui):
if loaded is not None and gui != "qt":
if qt_env2gui[loaded] != gui:
print(
f"Cannot switch Qt versions for this session; must use {qt_env2gui[loaded]}."
f"Cannot switch Qt versions for this session; will use {qt_env2gui[loaded]}."
)
return
return qt_env2gui[loaded]

if qt_api is not None and gui != "qt":
if qt_env2gui[qt_api] != gui:
print(
f'Request for "{gui}" will be ignored because `QT_API` '
f'environment variable is set to "{qt_api}"'
)
return qt_env2gui[qt_api]
else:
if gui == "qt5":
try:
Expand Down Expand Up @@ -112,6 +113,11 @@ def set_qt_api(gui):
print(f'Unrecognized Qt version: {gui}. Should be "qt5", "qt6", or "qt".')
return

# Import it now so we can figure out which version it is.
from IPython.external.qt_for_kernel import QT_API

return qt_env2gui[QT_API]


def get_inputhook_name_and_func(gui):
if gui in registered:
Expand All @@ -125,7 +131,7 @@ def get_inputhook_name_and_func(gui):

gui_mod = gui
if gui.startswith("qt"):
set_qt_api(gui)
gui = set_qt_api(gui)
gui_mod = "qt"

mod = importlib.import_module("IPython.terminal.pt_inputhooks." + gui_mod)
Expand Down
30 changes: 15 additions & 15 deletions IPython/terminal/tests/test_pt_inputhooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,18 +33,18 @@ def _get_qt_vers():
len(guis_avail) == 0, reason="No viable version of PyQt or PySide installed."
)
def test_inputhook_qt():
gui = guis_avail[0]

# Choose a qt version and get the input hook function. This will import Qt...
get_inputhook_name_and_func(gui)

# ...and now we're stuck with this version of Qt for good; can't switch.
for not_gui in ["qt6", "qt5"]:
if not_gui not in guis_avail:
break

with pytest.raises(ImportError):
get_inputhook_name_and_func(not_gui)

# A gui of 'qt' means "best available", or in this case, the last one that was used.
get_inputhook_name_and_func("qt")
# Choose the "best" Qt version.
gui_ret, _ = get_inputhook_name_and_func("qt")

assert gui_ret != "qt" # you get back the specific version that was loaded.
assert gui_ret in guis_avail

if len(guis_avail) > 2:
# ...and now we're stuck with this version of Qt for good; can't switch.
for not_gui in ["qt6", "qt5"]:
if not_gui != gui_ret:
break
# Try to import the other gui; it won't work.
gui_ret2, _ = get_inputhook_name_and_func(not_gui)
assert gui_ret2 == gui_ret
assert gui_ret2 != not_gui