Skip to content

Commit 303c193

Browse files
jwahlstrandnhz2IanButterworth
authored
Gtk4 port (#223)
Gtk -> Gtk4 Disables some precompile code since it was causing an issue on MacOS: ProfileView wasn't working after precompiling without restarting Julia. Co-authored-by: Nathan Zimmerberg <[email protected]> Co-authored-by: Ian Butterworth <[email protected]>
1 parent 9df9a86 commit 303c193

File tree

3 files changed

+117
-93
lines changed

3 files changed

+117
-93
lines changed

Project.toml

+3-3
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
1010
FileIO = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549"
1111
FlameGraphs = "08572546-2f56-4bcf-ba4e-bab62c3a3f89"
1212
Graphics = "a2bd30eb-e257-5431-a919-1863eab51364"
13-
Gtk = "4c0ca9eb-093a-5379-98c5-f87ac0bbbf44"
13+
Gtk4 = "9db2cae5-386f-4011-9d63-a5602296539b"
1414
GtkObservables = "8710efd8-4ad6-11eb-33ea-2d5ceb25a41c"
1515
InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240"
1616
IntervalSets = "8197267c-284f-5f27-9208-e0e47529a953"
@@ -27,8 +27,8 @@ Colors = "0.9, 0.10, 0.11, 0.12"
2727
FileIO = "1.6"
2828
FlameGraphs = "0.2.10, 1"
2929
Graphics = "0.4, 1"
30-
Gtk = "1.2"
31-
GtkObservables = "1"
30+
Gtk4 = "0.5, 0.6"
31+
GtkObservables = "2"
3232
IntervalSets = "0.2, 0.3, 0.4, 0.5, 0.6, 0.7"
3333
MethodAnalysis = "0.4"
3434
Preferences = "1.2"

src/ProfileView.jl

+90-70
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,15 @@ using FlameGraphs.IndirectArrays
1111
using Base.StackTraces: StackFrame
1212
using MethodAnalysis
1313
using InteractiveUtils
14-
using Gtk.ShortNames, GtkObservables, Colors, FileIO, IntervalSets
14+
using Gtk4, GtkObservables, Colors, FileIO, IntervalSets
15+
import GtkObservables: Canvas
1516
import Cairo
1617
using Graphics
1718
using Preferences
1819
using Requires
1920

2021
using FlameGraphs: Node, NodeData
21-
using Gtk.GConstants.GdkModifierType: SHIFT, CONTROL, MOD1
22+
const CONTROL = Gtk4.ModifierType_CONTROL_MASK
2223

2324
export @profview, warntype_clicked, descend_clicked, ascend_clicked
2425
@deprecate warntype_last warntype_clicked
@@ -114,13 +115,13 @@ macro profview(ex)
114115
return quote
115116
Profile.clear()
116117
# pause the eventloop while profiling
117-
before = Gtk.is_eventloop_running()
118+
before = Gtk4.GLib.is_loop_running()
118119
dt = Dates.now()
119120
try
120-
Gtk.enable_eventloop(false, wait_stopped = true)
121+
Gtk4.GLib.stop_main_loop(true)
121122
@profile $(esc(ex))
122123
finally
123-
Gtk.enable_eventloop(before, wait_stopped = true)
124+
before && Gtk4.GLib.start_main_loop()
124125
end
125126
view(;windowname = "Profile - $(Time(round(dt, Second)))")
126127
end
@@ -139,7 +140,7 @@ function closeall()
139140
return nothing
140141
end
141142

142-
const window_wrefs = WeakKeyDict{Gtk.GtkWindowLeaf,Nothing}()
143+
const window_wrefs = WeakKeyDict{Gtk4.GtkWindowLeaf,Nothing}()
143144
const tabname_allthreads = Symbol("All Threads")
144145
const tabname_alltasks = Symbol("All Tasks")
145146

@@ -153,7 +154,7 @@ You have several options to control the output, of which the major ones are:
153154
- `fcolor`: an optional coloration function. The main options are `FlameGraphs.FlameColors`
154155
and `FlameGraphs.StackFrameCategory`.
155156
- `C::Bool = false`: if true, the graph will include stackframes from C code called by Julia.
156-
- `recur`: on Julia 1.4+, collapse recursive calls (see `Profile.print` for more detail)
157+
- `recur`: collapse recursive calls (see `Profile.print` for more detail)
157158
- `expand_threads::Bool = true`: Break down profiling by thread (true by default)
158159
- `expand_tasks::Bool = false`: Break down profiling of each thread by task (false by default)
159160
- `graphtype::Symbol = :default`: Control how the graph is shown. `:flame` displays from the bottom up, `:icicle` from
@@ -198,7 +199,7 @@ function view(data::Vector{UInt64}; lidict=nothing, kwargs...)
198199
end
199200
function view(; kwargs...)
200201
# pausing the event loop here to facilitate a fast retrieve
201-
data, lidict = Gtk.pause_eventloop() do
202+
data, lidict = Gtk4.GLib.pause_main_loop() do
202203
Profile.retrieve()
203204
end
204205
view(_theme_colors[_theme[]], data; lidict=lidict, kwargs...)
@@ -213,14 +214,14 @@ function view(g::Node{NodeData}; kwargs...)
213214
end
214215
function view(fcolor, g::Node{NodeData}; data=nothing, lidict=nothing, kwargs...)
215216
win, _ = viewgui(fcolor, g; data=data, lidict=lidict, kwargs...)
216-
Gtk.showall(win)
217+
win
217218
end
218219
function view(g_or_gdict::Union{Node{NodeData},NestedGraphDict}; kwargs...)
219220
view(_theme_colors[_theme[]], g_or_gdict; kwargs...)
220221
end
221222
function view(fcolor, g_or_gdict::Union{Node{NodeData},NestedGraphDict}; data=nothing, lidict=nothing, kwargs...)
222223
win, _ = viewgui(fcolor, g_or_gdict; data=data, lidict=lidict, kwargs...)
223-
Gtk.showall(win)
224+
win
224225
end
225226

226227
function viewgui(fcolor, g::Node{NodeData}; kwargs...)
@@ -233,67 +234,66 @@ function viewgui(fcolor, gdict::NestedGraphDict; data=nothing, lidict=nothing, w
233234
graphtype = _graphtype[]
234235
end
235236
thread_tabs = collect(keys(gdict))
236-
nb_threads = Notebook() # for holding the per-thread pages
237-
Gtk.GAccessor.scrollable(nb_threads, true)
238-
Gtk.GAccessor.show_tabs(nb_threads, length(thread_tabs) > 1)
237+
nb_threads = GtkNotebook() # for holding the per-thread pages
238+
Gtk4.scrollable(nb_threads, true)
239+
Gtk4.show_tabs(nb_threads, length(thread_tabs) > 1)
239240
sort!(thread_tabs, by = s -> something(tryparse(Int, string(s)), 0)) # sorts thread_tabs as [all threads, 1, 2, 3 ....]
240241

241242
for thread_tab in thread_tabs
242243
gdict_thread = gdict[thread_tab]
243244
task_tabs = collect(keys(gdict_thread))
244245
sort!(task_tabs, by = s -> s == tabname_alltasks ? "" : string(s)) # sorts thread_tabs as [all threads, 0xds ....]
245246

246-
nb_tasks = Notebook() # for holding the per-task pages
247-
Gtk.GAccessor.scrollable(nb_tasks, true)
248-
Gtk.GAccessor.show_tabs(nb_tasks, length(task_tabs) > 1)
247+
nb_tasks = GtkNotebook() # for holding the per-task pages
248+
Gtk4.scrollable(nb_tasks, true)
249+
Gtk4.show_tabs(nb_tasks, length(task_tabs) > 1)
249250
task_tab_num = 1
250251
for task_tab in task_tabs
251252
g = gdict_thread[task_tab]
252253
gsig = Observable(g) # allow substitution by the open dialog
253254
c = canvas(UserUnit)
254-
set_gtk_property!(widget(c), :expand, true)
255-
256-
f = Frame(c)
257-
tb = Toolbar()
258-
tb_open = ToolButton("gtk-open")
259-
Gtk.GAccessor.tooltip_text(tb_open, "open")
260-
tb_save_as = ToolButton("gtk-save-as")
261-
Gtk.GAccessor.tooltip_text(tb_save_as, "save")
262-
tb_zoom_fit = ToolButton("gtk-zoom-fit")
263-
Gtk.GAccessor.tooltip_text(tb_zoom_fit, "zoom to fit")
264-
tb_zoom_in = ToolButton("gtk-zoom-in")
265-
Gtk.GAccessor.tooltip_text(tb_zoom_in, "zoom in")
266-
tb_zoom_out = ToolButton("gtk-zoom-out")
267-
Gtk.GAccessor.tooltip_text(tb_zoom_out, "zoom out")
268-
tb_info = ToolButton("gtk-info")
269-
Gtk.GAccessor.tooltip_text(tb_info, "ProfileView tips")
270-
tb_text_item = ToolItem()
271-
Gtk.GAccessor.expand(tb_text_item, true)
272-
tb_text = Entry()
273-
Gtk.GAccessor.has_frame(tb_text, false)
274-
Gtk.GAccessor.sensitive(tb_text, false)
275-
push!(tb_text_item, tb_text)
255+
set_gtk_property!(widget(c), :vexpand, true)
256+
257+
f = GtkFrame(c)
258+
Gtk4.css_classes(f, ["squared"])
259+
tb = GtkBox(:h)
260+
tb_open = GtkButton(:icon_name,"document-open-symbolic")
261+
Gtk4.tooltip_text(tb_open, "open")
262+
tb_save_as = GtkButton(:icon_name,"document-save-as-symbolic")
263+
Gtk4.tooltip_text(tb_save_as, "save")
264+
tb_zoom_fit = GtkButton(:icon_name,"zoom-fit-best-symbolic")
265+
Gtk4.tooltip_text(tb_zoom_fit, "zoom to fit")
266+
tb_zoom_in = GtkButton(:icon_name,"zoom-in-symbolic")
267+
Gtk4.tooltip_text(tb_zoom_in, "zoom in")
268+
tb_zoom_out = GtkButton(:icon_name, "zoom-out-symbolic")
269+
Gtk4.tooltip_text(tb_zoom_out, "zoom out")
270+
tb_info = GtkButton(:icon_name, "dialog-information-symbolic")
271+
Gtk4.tooltip_text(tb_info, "ProfileView tips")
272+
tb_text = GtkEntry()
273+
Gtk4.has_frame(tb_text, false)
274+
Gtk4.sensitive(tb_text, false)
275+
tb_text.hexpand = true
276276

277277
push!(tb, tb_open)
278278
push!(tb, tb_save_as)
279-
push!(tb, SeparatorToolItem())
279+
push!(tb, GtkSeparator(:h))
280280
push!(tb, tb_zoom_fit)
281281
push!(tb, tb_zoom_out)
282282
push!(tb, tb_zoom_in)
283-
push!(tb, SeparatorToolItem())
283+
push!(tb, GtkSeparator(:h))
284284
push!(tb, tb_info)
285-
push!(tb, SeparatorToolItem())
286-
push!(tb, tb_text_item)
285+
push!(tb, GtkSeparator(:h))
286+
push!(tb, tb_text)
287287
# FIXME: likely have to do `allkwargs` in the open/save below (add in C, combine, recur)
288288
signal_connect(open_cb, tb_open, "clicked", Nothing, (), false, (widget(c),gsig,kwargs))
289289
signal_connect(save_as_cb, tb_save_as, "clicked", Nothing, (), false, (widget(c),data,lidict,g))
290-
signal_connect(info_cb, tb_info, "clicked", Nothing, (), false, ())
290+
signal_connect(info_cb, tb_info, "clicked", Nothing, (), false, (widget(c),))
291291

292-
bx = Box(:v)
292+
bx = GtkBox(:v)
293293
push!(bx, tb)
294294
push!(bx, f)
295295
# don't use the actual taskid as the tab as it's very long
296-
push!(nb_tasks, bx, task_tab_num == 1 ? task_tab : Symbol(task_tab_num - 1))
296+
push!(nb_tasks, bx, task_tab_num == 1 ? string(task_tab) : string(task_tab_num - 1))
297297
fdraw = viewprof(fcolor, c, gsig, (tb_zoom_fit, tb_zoom_out, tb_zoom_in, tb_text), graphtype; kwargs...)
298298
GtkObservables.gc_preserve(nb_threads, c)
299299
GtkObservables.gc_preserve(nb_threads, fdraw)
@@ -303,12 +303,12 @@ function viewgui(fcolor, gdict::NestedGraphDict; data=nothing, lidict=nothing, w
303303
push!(nb_threads, nb_tasks, string(thread_tab))
304304
end
305305

306-
bx = Box(:v)
306+
bx = GtkBox(:v)
307307
push!(bx, nb_threads)
308308

309309
# Defer creating the window until here because Window includes a `show` that will unpause the Gtk eventloop
310-
win = Window(windowname, 800, 600)
311-
push!(win, bx)
310+
win = GtkWindow(windowname, 800, 600)
311+
win[] = bx
312312

313313
# Register the window with closeall
314314
window_wrefs[win] = nothing
@@ -317,12 +317,8 @@ function viewgui(fcolor, gdict::NestedGraphDict; data=nothing, lidict=nothing, w
317317
end
318318

319319
# Ctrl-w and Ctrl-q destroy the window
320-
signal_connect(win, "key-press-event") do w, evt
321-
if evt.state == CONTROL && (evt.keyval == UInt('q') || evt.keyval == UInt('w'))
322-
@async destroy(w)
323-
nothing
324-
end
325-
end
320+
kc = GtkEventControllerKey(win)
321+
signal_connect(close_cb, kc, "key-pressed", Cint, (UInt32, UInt32, UInt32), false, (win))
326322

327323
return win, _c, _fdraw, (_tb_open, _tb_save_as)
328324
end
@@ -369,7 +365,7 @@ function viewprof_func(fcolor, c, g, fontsize, tb_items, graphtype)
369365
img, tagimg = img[:,2:end], discardfirstcol(tagimg)
370366
img24 = RGB24.(img)
371367
img24 = img24[:,end:-1:1]
372-
fv = XY(0.0..size(img24,1), 0.0..size(img24,2))
368+
fv = XY(0.5..size(img24,1)-0.5, 0.5..size(img24,2)-0.5)
373369
zr = Observable(ZoomRegion(fv, fv))
374370
signal_connect(zoom_fit_cb, tb_zoom_fit, "clicked", Nothing, (), false, (zr))
375371
signal_connect(zoom_out_cb, tb_zoom_out, "clicked", Nothing, (), false, (zr))
@@ -393,7 +389,7 @@ function viewprof_func(fcolor, c, g, fontsize, tb_items, graphtype)
393389
lasttextbb = Ref(BoundingBox(1,0,1,0))
394390
sigmotion = on(c.mouse.motion) do btn
395391
# Repair image from ovewritten text
396-
if c.widget.is_realized && c.widget.is_sized
392+
if c.widget.is_sized
397393
ctx = getgc(c)
398394
if Graphics.width(lasttextbb[]) > 0
399395
r = zr[]
@@ -410,17 +406,18 @@ function viewprof_func(fcolor, c, g, fontsize, tb_items, graphtype)
410406
# Write the info
411407
xu, yu = btn.position.x, btn.position.y
412408
sf = gettag(tagimg, xu, yu)
409+
b = Gtk4.buffer(tb_text)
413410
if sf != StackTraces.UNKNOWN
414411
str_long = long_info_str(sf)
415-
Gtk.GAccessor.text(tb_text, str_long)
412+
b[String] = str_long
416413
str = string(basename(string(sf.file)), ", ", sf.func, ": line ", sf.line)
417414
set_source(ctx, fcolor(:font))
418415
Cairo.set_font_face(ctx, "sans-serif $(fontsize)px")
419416
xi = zr[].currentview.x
420417
xmin, xmax = minimum(xi), maximum(xi)
421418
lasttextbb[] = deform(Cairo.text(ctx, xu, yu, str, halign = xu < (2xmin+xmax)/3 ? "left" : xu < (xmin+2xmax)/3 ? "center" : "right"), -2, 2, -2, 2)
422419
else
423-
Gtk.GAccessor.text(tb_text, "")
420+
b[String]=""
424421
end
425422
reveal(c)
426423
end
@@ -466,9 +463,11 @@ end
466463

467464
@guarded function open_cb(::Ptr, settings::Tuple)
468465
c, gsig, kwargs = settings
469-
selection = open_dialog("Load profile data", toplevel(c), ("*.jlprof","*"))
470-
isempty(selection) && return nothing
471-
return _open(gsig, selection; kwargs...)
466+
open_dialog("Load profile data", toplevel(c), ("*.jlprof","*")) do selection
467+
isempty(selection) && return nothing
468+
_open(gsig, selection; kwargs...)
469+
end
470+
return nothing
472471
end
473472

474473
function _open(gsig, selection; kwargs...)
@@ -484,12 +483,15 @@ end
484483

485484
@guarded function save_as_cb(::Ptr, profdata::Tuple)
486485
c, data, lidict, g = profdata
487-
selection = save_dialog("Save profile data as *.jlprof file", toplevel(c), ("*.jlprof",))
488-
isempty(selection) && return nothing
489-
if data === nothing && lidict === nothing
490-
return _save(selection, g)
486+
save_dialog("Save profile data as *.jlprof file", toplevel(c), ("*.jlprof",)) do selection
487+
isempty(selection) && return nothing
488+
if data === nothing && lidict === nothing
489+
_save(selection, g)
490+
else
491+
_save(selection, data, lidict)
492+
end
491493
end
492-
return _save(selection, data, lidict)
494+
return nothing
493495
end
494496

495497
function _save(selection, args...)
@@ -512,7 +514,7 @@ end
512514
return nothing
513515
end
514516

515-
@guarded function info_cb(::Ptr, ::Tuple)
517+
@guarded function info_cb(::Ptr, win::Tuple)
516518
# Note: Keep this updated with the readme
517519
info = """
518520
ProfileView.jl Interface Tips
@@ -542,10 +544,22 @@ end
542544
543545
Color theme: The color theme used for the graph is `:light`, which can be changed to `:dark` via `ProfileView.set_theme!(:dark)`
544546
"""
545-
info_dialog(info)
547+
info_dialog(info, toplevel(win[1])) do
548+
nothing
549+
end
546550
return nothing
547551
end
548552

553+
unhandled = convert(Cint, false)
554+
555+
@guarded unhandled function close_cb(::Ptr, keyval::UInt32, keycode::UInt32, state::UInt32, win::GtkWindow)
556+
if (ModifierType(state & Gtk4.MODIFIER_MASK) & CONTROL == CONTROL) && (keyval == UInt('q') || keyval == UInt('w'))
557+
@async Gtk4.destroy(win)
558+
return Cint(1)
559+
end
560+
return Cint(0)
561+
end
562+
549563
discardfirstcol(A) = A[:,2:end]
550564
discardfirstcol(A::IndirectArray) = IndirectArray(A.index[:,2:end], A.values)
551565

@@ -577,6 +591,12 @@ function __init__()
577591
printstyled(io, "\n`using Cthulhu` is required for `$(exc.f)`"; color=:yellow)
578592
end
579593
end
594+
# by default GtkFrame uses rounded corners
595+
css="""
596+
.squared {border-radius: 0;}
597+
"""
598+
cssprov=GtkCssProvider(css)
599+
push!(GdkDisplay(), cssprov, Gtk4.STYLE_PROVIDER_PRIORITY_APPLICATION)
580600
end
581601

582602
using PrecompileTools
@@ -607,12 +627,12 @@ let
607627
win, c, fdraw = viewgui(FlameGraphs.default_colors, gdict)
608628
for obs in c.preserved
609629
if isa(obs, Observable) || isa(obs, Observables.ObserverFunction)
610-
precompile(obs)
630+
# FIXME: on MacOS, the following seems to prevent ProfileView from working in the same session where precompiling was done
631+
# precompile(obs)
611632
end
612633
end
613634
precompile(fdraw)
614635
closeall() # necessary to prevent serialization of stale references (including the internal `empty!`)
615-
Gtk.enable_eventloop(false, wait_stopped = true) # to avoid trailing task warning
616636
end
617637
end
618638
end

0 commit comments

Comments
 (0)