Skip to content

Commit c821297

Browse files
authored
Add PySide6 support to gui.jl (#634)
* Add PySide6 support to gui.jl - Update fix_qt_plugin_path() to support qt6.conf files used by PySide6 - Use Libraries path from qt6.conf instead of Prefix for Qt6 plugin path - Set QT_PLUGIN_PATH to {libraries}/qt6/plugins for PySide6 - Add PySide6 to event loop callback function - Add PySide6 module hook for automatic plugin path fixing - Update documentation to include PySide6 support Fixes #632 * Add PySide6 to GUI event loop tests --------- Co-authored-by: Christopher Doris <github.com/cjdoris>
1 parent c75ce08 commit c821297

File tree

2 files changed

+42
-16
lines changed

2 files changed

+42
-16
lines changed

src/Compat/gui.jl

Lines changed: 41 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,26 +7,49 @@ This fixes the problem that Qt does not know where to find its `qt.conf` file, b
77
always looks relative to `sys.executable`, which can be the Julia executable not the Python
88
one when using this package.
99
10-
If `CONFIG.auto_fix_qt_plugin_path` is true, then this is run automatically before `PyQt4`, `PyQt5`, `PySide` or `PySide2` are imported.
10+
If `CONFIG.auto_fix_qt_plugin_path` is true, then this is run automatically before `PyQt4`, `PyQt5`, `PySide`, `PySide2` or `PySide6` are imported.
1111
"""
1212
function fix_qt_plugin_path()
1313
C.CTX.exe_path === nothing && return false
1414
e = pyosmodule.environ
1515
"QT_PLUGIN_PATH" in e && return false
16-
qtconf = joinpath(dirname(C.CTX.exe_path::AbstractString), "qt.conf")
17-
isfile(qtconf) || return false
18-
for line in eachline(qtconf)
19-
m = match(r"^\s*prefix\s*=(.*)$"i, line)
20-
if m !== nothing
21-
path = strip(m.captures[1]::AbstractString)
22-
path[1] == path[end] == '"' && (path = path[2:end-1])
23-
path = joinpath(path, "plugins")
24-
if isdir(path)
25-
e["QT_PLUGIN_PATH"] = realpath(path)
26-
return true
16+
17+
exe_dir = dirname(C.CTX.exe_path::AbstractString)
18+
19+
# Check for Qt6 configuration first (PySide6)
20+
qt6conf = joinpath(exe_dir, "qt6.conf")
21+
if isfile(qt6conf)
22+
for line in eachline(qt6conf)
23+
m = match(r"^\s*Libraries\s*=(.*)$"i, line)
24+
if m !== nothing
25+
path = strip(m.captures[1]::AbstractString)
26+
path[1] == path[end] == '"' && (path = path[2:end-1])
27+
path = joinpath(path, "qt6", "plugins")
28+
if isdir(path)
29+
e["QT_PLUGIN_PATH"] = realpath(path)
30+
return true
31+
end
32+
end
33+
end
34+
end
35+
36+
# Check for Qt5 configuration (PyQt4, PyQt5, PySide, PySide2)
37+
qtconf = joinpath(exe_dir, "qt.conf")
38+
if isfile(qtconf)
39+
for line in eachline(qtconf)
40+
m = match(r"^\s*prefix\s*=(.*)$"i, line)
41+
if m !== nothing
42+
path = strip(m.captures[1]::AbstractString)
43+
path[1] == path[end] == '"' && (path = path[2:end-1])
44+
path = joinpath(path, "plugins")
45+
if isdir(path)
46+
e["QT_PLUGIN_PATH"] = realpath(path)
47+
return true
48+
end
2749
end
2850
end
2951
end
52+
3053
return false
3154
end
3255

@@ -65,7 +88,7 @@ function init_gui()
6588
pyexec(
6689
"""
6790
def new_event_loop_callback(g, interval=0.04):
68-
if g in ("pyqt4","pyqt5","pyside","pyside2"):
91+
if g in ("pyqt4","pyqt5","pyside","pyside2","pyside6"):
6992
if g == "pyqt4":
7093
import PyQt4.QtCore as QtCore
7194
elif g == "pyqt5":
@@ -74,6 +97,8 @@ function init_gui()
7497
import PySide.QtCore as QtCore
7598
elif g == "pyside2":
7699
import PySide2.QtCore as QtCore
100+
elif g == "pyside6":
101+
import PySide6.QtCore as QtCore
77102
instance = QtCore.QCoreApplication.instance
78103
AllEvents = QtCore.QEventLoop.AllEvents
79104
processEvents = QtCore.QCoreApplication.processEvents
@@ -139,6 +164,7 @@ function init_gui()
139164
pymodulehooks.add_hook("PyQt5", fixqthook)
140165
pymodulehooks.add_hook("PySide", fixqthook)
141166
pymodulehooks.add_hook("PySide2", fixqthook)
167+
pymodulehooks.add_hook("PySide6", fixqthook)
142168
end
143169
end
144170

@@ -161,11 +187,11 @@ Activate an event loop for the GUI framework `g`, so that the framework can run
161187
162188
The event loop runs every `interval` seconds. If `fix` is true and `g` is a Qt framework, then [`fix_qt_plugin_path`](@ref PythonCall.fix_qt_plugin_path) is called.
163189
164-
Supported values of `g` (and the Python module they relate to) are: `:pyqt4` (PyQt4), `:pyqt5` (PyQt5), `:pyside` (PySide), `:pyside2` (PySide2), `:gtk` (gtk), `:gtk3` (gi), `:wx` (wx), `:tkinter` (tkinter).
190+
Supported values of `g` (and the Python module they relate to) are: `:pyqt4` (PyQt4), `:pyqt5` (PyQt5), `:pyside` (PySide), `:pyside2` (PySide2), `:pyside6` (PySide6), `:gtk` (gtk), `:gtk3` (gi), `:wx` (wx), `:tkinter` (tkinter).
165191
"""
166192
function event_loop_on(g::Symbol; interval::Real = 0.04, fix::Bool = false)
167193
haskey(EVENT_LOOPS, g) && return EVENT_LOOPS[g]
168-
fix && g in (:pyqt4, :pyqt5, :pyside, :pyside2) && fix_qt_plugin_path()
194+
fix && g in (:pyqt4, :pyqt5, :pyside, :pyside2, :pyside6) && fix_qt_plugin_path()
169195
callback = new_event_loop_callback(string(g), Float64(interval))
170196
EVENT_LOOPS[g] = Timer(t -> callback(), 0; interval = interval)
171197
end

test/Compat.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
@test PythonCall.fix_qt_plugin_path() === false
66
end
77
@testset "event_loop_on/off" begin
8-
for g in [:pyqt4, :pyqt5, :pyside, :pyside2, :gtk, :gtk3, :wx]
8+
for g in [:pyqt4, :pyqt5, :pyside, :pyside2, :pyside6, :gtk, :gtk3, :wx]
99
# TODO: actually test the various GUIs somehow?
1010
@show g
1111
@test_throws PyException PythonCall.event_loop_on(g)

0 commit comments

Comments
 (0)