Skip to content

Commit 84624da

Browse files
author
Christopher Doris
committed
code reorg; added gui event loops
1 parent 00dea2a commit 84624da

File tree

7 files changed

+129
-113
lines changed

7 files changed

+129
-113
lines changed

src/PythonCall.jl

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,11 +69,13 @@ include("pywrap/PyIO.jl")
6969
include("pywrap/PyTable.jl")
7070
include("pywrap/PyPandasDataFrame.jl")
7171
# misc
72-
include("with.jl")
73-
include("multimedia.jl")
7472
include("pyconst_macro.jl")
7573
include("juliacall.jl")
76-
include("serialization.jl")
74+
include("compat/stdlib.jl")
75+
include("compat/with.jl")
76+
include("compat/multimedia.jl")
77+
include("compat/serialization.jl")
78+
include("compat/gui.jl")
7779

7880
function __init__()
7981
C.with_gil() do
@@ -95,6 +97,8 @@ function __init__()
9597
init_jlwrap_number()
9698
init_jlwrap_io()
9799
init_juliacall_2()
100+
init_stdlib()
101+
init_gui()
98102
end
99103
end
100104

src/old/gui.jl renamed to src/compat/gui.jl

Lines changed: 71 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@ one when using this package.
1010
If `CONFIG.qtfix` is true, then this is run automatically before `PyQt4`, `PyQt5`, `PySide` or `PySide2` are imported.
1111
"""
1212
function fix_qt_plugin_path()
13-
CONFIG.exepath === nothing && return false
14-
e = pyosmodule().environ
13+
C.CTX.exe_path === nothing && return false
14+
e = pyosmodule.environ
1515
"QT_PLUGIN_PATH" in e && return false
16-
qtconf = joinpath(dirname(CONFIG.exepath), "qt.conf")
16+
qtconf = joinpath(dirname(C.CTX.exe_path), "qt.conf")
1717
isfile(qtconf) || return false
1818
for line in eachline(qtconf)
1919
m = match(r"^\s*prefix\s*=(.*)$"i, line)
@@ -30,58 +30,39 @@ function fix_qt_plugin_path()
3030
return false
3131
end
3232

33-
"""
34-
pyinteract(; force=false, sleep=0.1)
33+
# """
34+
# pyinteract(; force=false, sleep=0.1)
3535

36-
Some Python GUIs can work interactively, meaning the GUI is available but the interactive prompt is returned (e.g. after calling `matplotlib.pyplot.ion()`).
37-
To use these from Julia, currently you must manually call `pyinteract()` each time you want to interact.
36+
# Some Python GUIs can work interactively, meaning the GUI is available but the interactive prompt is returned (e.g. after calling `matplotlib.pyplot.ion()`).
37+
# To use these from Julia, currently you must manually call `pyinteract()` each time you want to interact.
3838

39-
Internally, this is calling the `PyOS_InputHook` asynchronously. Only one copy is run at a time unless `force` is true.
39+
# Internally, this is calling the `PyOS_InputHook` asynchronously. Only one copy is run at a time unless `force` is true.
4040

41-
The asynchronous task waits for `sleep` seconds before calling the hook function.
42-
This gives time for the next prompt to be printed and waiting for input.
43-
As a result, there will be a small delay before the GUI becomes interactive.
44-
"""
45-
pyinteract(; force::Bool = false, sleep::Real = 0.1) =
46-
if !CONFIG.inputhookrunning || force
47-
CONFIG.inputhookrunning = true
48-
@async begin
49-
sleep > 0 && Base.sleep(sleep)
50-
C.PyOS_RunInputHook()
51-
CONFIG.inputhookrunning = false
52-
end
53-
nothing
54-
end
55-
export pyinteract
41+
# The asynchronous task waits for `sleep` seconds before calling the hook function.
42+
# This gives time for the next prompt to be printed and waiting for input.
43+
# As a result, there will be a small delay before the GUI becomes interactive.
44+
# """
45+
# pyinteract(; force::Bool = false, sleep::Real = 0.1) =
46+
# if !CONFIG.inputhookrunning || force
47+
# CONFIG.inputhookrunning = true
48+
# @async begin
49+
# sleep > 0 && Base.sleep(sleep)
50+
# C.PyOS_RunInputHook()
51+
# CONFIG.inputhookrunning = false
52+
# end
53+
# nothing
54+
# end
55+
# export pyinteract
5656

5757
const EVENT_LOOPS = Dict{Symbol,Base.Timer}()
5858

59-
"""
60-
event_loop_off(g::Symbol)
59+
const new_event_loop_callback = pynew()
6160

62-
Terminate the event loop `g` if it is running.
63-
"""
64-
function event_loop_off(g::Symbol)
65-
if haskey(EVENT_LOOPS, g)
66-
Base.close(pop!(EVENT_LOOPS, g))
67-
end
68-
return
69-
end
70-
71-
"""
72-
event_loop_on(g::Symbol; interval=40e-3, fix=false)
73-
74-
Activate an event loop for the GUI framework `g`, so that the framework can run in the background of a Julia session.
75-
76-
The event loop runs every `interval` seconds. If `fix` is true and `g` is a Qt framework, then [`fix_qt_plugin_path`](@ref) is called.
77-
78-
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).
79-
"""
80-
function event_loop_on(g::Symbol; interval::Real = 40e-3, fix::Bool = false)
81-
haskey(EVENT_LOOPS, g) && return EVENT_LOOPS[g]
82-
fix && g in (:pyqt4, :pyqt5, :pyside, :pyside2) && fix_qt_plugin_path()
83-
@py ```
84-
def make_event_loop(g, interval):
61+
function init_gui()
62+
# define callbacks
63+
@py g = {}
64+
@py @exec """
65+
def new_event_loop_callback(g, interval=0.04):
8566
if g in ("pyqt4","pyqt5","pyside","pyside2"):
8667
if g == "pyqt4":
8768
import PyQt4.QtCore as QtCore
@@ -95,7 +76,7 @@ function event_loop_on(g::Symbol; interval::Real = 40e-3, fix::Bool = false)
9576
AllEvents = QtCore.QEventLoop.AllEvents
9677
processEvents = QtCore.QCoreApplication.processEvents
9778
maxtime = interval * 1000
98-
def event_loop():
79+
def callback():
9980
app = instance()
10081
if app is not None:
10182
app._in_event_loop = True
@@ -110,15 +91,15 @@ function event_loop_on(g::Symbol; interval::Real = 40e-3, fix::Bool = false)
11091
import gtk
11192
events_pending = gtk.events_pending
11293
main_iteration = gtk.main_iteration
113-
def event_loop():
94+
def callback():
11495
while events_pending():
11596
main_iteration()
11697
elif g == "wx":
11798
import wx
11899
GetApp = wx.GetApp
119100
EventLoop = wx.EventLoop
120101
EventLoopActivator = wx.EventLoopActivator
121-
def event_loop():
102+
def callback():
122103
app = GetApp()
123104
if app is not None:
124105
app._in_event_loop = True
@@ -133,7 +114,7 @@ function event_loop_on(g::Symbol; interval::Real = 40e-3, fix::Bool = false)
133114
import tkinter, _tkinter
134115
flag = _tkinter.ALL_EVENTS | _tkinter.DONT_WAIT
135116
root = None
136-
def event_loop():
117+
def callback():
137118
global root
138119
new_root = tkinter._default_root
139120
if new_root is not None:
@@ -143,8 +124,42 @@ function event_loop_on(g::Symbol; interval::Real = 40e-3, fix::Bool = false)
143124
pass
144125
else:
145126
raise ValueError("invalid event loop name: {}".format(g))
146-
return event_loop
147-
$event_loop = make_event_loop($(string(g)), $interval)
148-
```
149-
EVENT_LOOPS[g] = Timer(t -> event_loop(), 0; interval = interval)
127+
return callback
128+
""" g
129+
pycopy!(new_event_loop_callback, g["new_event_loop_callback"])
130+
131+
# add a hook to automatically call fix_qt_plugin_path()
132+
fixqthook = Py(() -> (fix_qt_plugin_path(); nothing))
133+
pymodulehooks.add_hook("PyQt4", fixqthook)
134+
pymodulehooks.add_hook("PyQt5", fixqthook)
135+
pymodulehooks.add_hook("PySide", fixqthook)
136+
pymodulehooks.add_hook("PySide2", fixqthook)
137+
end
138+
139+
"""
140+
event_loop_off(g::Symbol)
141+
142+
Terminate the event loop `g` if it is running.
143+
"""
144+
function event_loop_off(g::Symbol)
145+
if haskey(EVENT_LOOPS, g)
146+
Base.close(pop!(EVENT_LOOPS, g))
147+
end
148+
return
149+
end
150+
151+
"""
152+
event_loop_on(g::Symbol; interval=0.04, fix=false)
153+
154+
Activate an event loop for the GUI framework `g`, so that the framework can run in the background of a Julia session.
155+
156+
The event loop runs every `interval` seconds. If `fix` is true and `g` is a Qt framework, then [`fix_qt_plugin_path`](@ref) is called.
157+
158+
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).
159+
"""
160+
function event_loop_on(g::Symbol; interval::Real = 0.04, fix::Bool = false)
161+
haskey(EVENT_LOOPS, g) && return EVENT_LOOPS[g]
162+
fix && g in (:pyqt4, :pyqt5, :pyside, :pyside2) && fix_qt_plugin_path()
163+
callback = new_event_loop_callback(string(g), Float64(interval))
164+
EVENT_LOOPS[g] = Timer(t -> callback(), 0; interval = interval)
150165
end
File renamed without changes.
File renamed without changes.

src/compat/stdlib.jl

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
const pymodulehooks = pynew()
2+
3+
function init_stdlib()
4+
5+
# check word size
6+
pywordsize = @py(jlbool(pysysmodule.maxsize > 2^32)) ? 64 : 32
7+
pywordsize == Sys.WORD_SIZE || error("Julia is $(Sys.WORD_SIZE)-bit but Python is $(pywordsize)-bit")
8+
9+
if !C.CTX.is_embedded
10+
11+
# set sys.argv
12+
pysysmodule.argv = [""]
13+
pysysmodule.argv.extend(pylist(ARGS))
14+
15+
# some modules test for interactivity by checking if sys.ps1 exists
16+
if isinteractive() && !pyhasattr(pysysmodule, "ps1")
17+
pysysmodule.ps1 = ">>> "
18+
end
19+
20+
# add hook to perform certain actions when certain modules are loaded
21+
@py g = {}
22+
@py @exec """
23+
import sys
24+
class JuliaCompatHooks:
25+
def __init__(self):
26+
self.hooks = {}
27+
def find_module(self, name, path=None):
28+
hs = self.hooks.get(name)
29+
if hs is not None:
30+
for h in hs:
31+
h()
32+
def add_hook(self, name, h):
33+
if name not in self.hooks:
34+
self.hooks[name] = [h]
35+
else:
36+
self.hooks[name].append(h)
37+
if name in sys.modules:
38+
h()
39+
JULIA_COMPAT_HOOKS = JuliaCompatHooks()
40+
sys.meta_path.insert(0, JULIA_COMPAT_HOOKS)
41+
""" g
42+
pycopy!(pymodulehooks, g["JULIA_COMPAT_HOOKS"])
43+
44+
# @require IJulia = "7073ff75-c697-5162-941a-fcdaad2a7d2a" begin
45+
# IJulia.push_postexecute_hook() do
46+
# CONFIG.pyplotautoshow && pyplotshow()
47+
# end
48+
# end
49+
end
50+
51+
end
File renamed without changes.

src/cpython/context.jl

Lines changed: 0 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -210,23 +210,6 @@ function init_context()
210210

211211
with_gil() do
212212

213-
# @pyg `import sys, os`
214-
215-
# pywordsize = (@pyv `sys.maxsize > 2**32`::Bool) ? 64 : 32
216-
# pywordsize == Sys.WORD_SIZE || error("Julia is $(Sys.WORD_SIZE)-bit but Python is $(pywordsize)-bit (at $(CONFIG.exepath ? "unknown location" : CONFIG.exepath))")
217-
218-
# if !CONFIG.isembedded
219-
# @py ```
220-
# # Some modules expect sys.argv to be set
221-
# sys.argv = [""]
222-
# sys.argv.extend($ARGS)
223-
224-
# # Some modules test for interactivity by checking if sys.ps1 exists
225-
# if $(isinteractive()) and not hasattr(sys, "ps1"):
226-
# sys.ps1 = ">>> "
227-
# ```
228-
# end
229-
230213
# Get the python version
231214
verstr = Base.unsafe_string(Py_GetVersion())
232215
vermatch = match(r"^[0-9.]+", verstr)
@@ -238,43 +221,6 @@ function init_context()
238221
"Only Python 3 is supported, this is Python $(CTX.version) at $(CTX.exe_path===missing ? "unknown location" : CTX.exe_path).",
239222
)
240223

241-
# # EXPERIMENTAL: hooks to perform actions when certain modules are loaded
242-
# if !CONFIG.isembedded
243-
# @py ```
244-
# import sys
245-
# class JuliaCompatHooks:
246-
# def __init__(self):
247-
# self.hooks = {}
248-
# def find_module(self, name, path=None):
249-
# hs = self.hooks.get(name)
250-
# if hs is not None:
251-
# for h in hs:
252-
# h()
253-
# def add_hook(self, name, h):
254-
# if name not in self.hooks:
255-
# self.hooks[name] = [h]
256-
# else:
257-
# self.hooks[name].append(h)
258-
# if name in sys.modules:
259-
# h()
260-
# JULIA_COMPAT_HOOKS = JuliaCompatHooks()
261-
# sys.meta_path.insert(0, JULIA_COMPAT_HOOKS)
262-
263-
# # Before Qt is loaded, fix the path used to look up its plugins
264-
# qtfix_hook = $(() -> (CONFIG.qtfix && fix_qt_plugin_path(); nothing))
265-
# JULIA_COMPAT_HOOKS.add_hook("PyQt4", qtfix_hook)
266-
# JULIA_COMPAT_HOOKS.add_hook("PyQt5", qtfix_hook)
267-
# JULIA_COMPAT_HOOKS.add_hook("PySide", qtfix_hook)
268-
# JULIA_COMPAT_HOOKS.add_hook("PySide2", qtfix_hook)
269-
# ```
270-
271-
# @require IJulia = "7073ff75-c697-5162-941a-fcdaad2a7d2a" begin
272-
# IJulia.push_postexecute_hook() do
273-
# CONFIG.pyplotautoshow && pyplotshow()
274-
# end
275-
# end
276-
# end
277-
278224
# # EXPERIMENTAL: IPython integration
279225
# if CONFIG.isembedded && CONFIG.ipythonintegration
280226
# if !CONFIG.isipython

0 commit comments

Comments
 (0)