@@ -11,14 +11,15 @@ using FlameGraphs.IndirectArrays
11
11
using Base. StackTraces: StackFrame
12
12
using MethodAnalysis
13
13
using InteractiveUtils
14
- using Gtk. ShortNames, GtkObservables, Colors, FileIO, IntervalSets
14
+ using Gtk4, GtkObservables, Colors, FileIO, IntervalSets
15
+ import GtkObservables: Canvas
15
16
import Cairo
16
17
using Graphics
17
18
using Preferences
18
19
using Requires
19
20
20
21
using FlameGraphs: Node, NodeData
21
- using Gtk . GConstants . GdkModifierType : SHIFT, CONTROL, MOD1
22
+ const CONTROL = Gtk4 . ModifierType_CONTROL_MASK
22
23
23
24
export @profview , warntype_clicked, descend_clicked, ascend_clicked
24
25
@deprecate warntype_last warntype_clicked
@@ -114,13 +115,13 @@ macro profview(ex)
114
115
return quote
115
116
Profile. clear ()
116
117
# pause the eventloop while profiling
117
- before = Gtk . is_eventloop_running ()
118
+ before = Gtk4 . GLib . is_loop_running ()
118
119
dt = Dates. now ()
119
120
try
120
- Gtk . enable_eventloop ( false , wait_stopped = true )
121
+ Gtk4 . GLib . stop_main_loop ( true )
121
122
@profile $ (esc (ex))
122
123
finally
123
- Gtk . enable_eventloop ( before, wait_stopped = true )
124
+ before && Gtk4 . GLib . start_main_loop ( )
124
125
end
125
126
view (;windowname = " Profile - $(Time (round (dt, Second))) " )
126
127
end
@@ -139,7 +140,7 @@ function closeall()
139
140
return nothing
140
141
end
141
142
142
- const window_wrefs = WeakKeyDict {Gtk .GtkWindowLeaf,Nothing} ()
143
+ const window_wrefs = WeakKeyDict {Gtk4 .GtkWindowLeaf,Nothing} ()
143
144
const tabname_allthreads = Symbol (" All Threads" )
144
145
const tabname_alltasks = Symbol (" All Tasks" )
145
146
@@ -153,7 +154,7 @@ You have several options to control the output, of which the major ones are:
153
154
- `fcolor`: an optional coloration function. The main options are `FlameGraphs.FlameColors`
154
155
and `FlameGraphs.StackFrameCategory`.
155
156
- `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)
157
158
- `expand_threads::Bool = true`: Break down profiling by thread (true by default)
158
159
- `expand_tasks::Bool = false`: Break down profiling of each thread by task (false by default)
159
160
- `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...)
198
199
end
199
200
function view (; kwargs... )
200
201
# 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
202
203
Profile. retrieve ()
203
204
end
204
205
view (_theme_colors[_theme[]], data; lidict= lidict, kwargs... )
@@ -213,14 +214,14 @@ function view(g::Node{NodeData}; kwargs...)
213
214
end
214
215
function view (fcolor, g:: Node{NodeData} ; data= nothing , lidict= nothing , kwargs... )
215
216
win, _ = viewgui (fcolor, g; data= data, lidict= lidict, kwargs... )
216
- Gtk . showall ( win)
217
+ win
217
218
end
218
219
function view (g_or_gdict:: Union{Node{NodeData},NestedGraphDict} ; kwargs... )
219
220
view (_theme_colors[_theme[]], g_or_gdict; kwargs... )
220
221
end
221
222
function view (fcolor, g_or_gdict:: Union{Node{NodeData},NestedGraphDict} ; data= nothing , lidict= nothing , kwargs... )
222
223
win, _ = viewgui (fcolor, g_or_gdict; data= data, lidict= lidict, kwargs... )
223
- Gtk . showall ( win)
224
+ win
224
225
end
225
226
226
227
function viewgui (fcolor, g:: Node{NodeData} ; kwargs... )
@@ -233,67 +234,66 @@ function viewgui(fcolor, gdict::NestedGraphDict; data=nothing, lidict=nothing, w
233
234
graphtype = _graphtype[]
234
235
end
235
236
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 )
239
240
sort! (thread_tabs, by = s -> something (tryparse (Int, string (s)), 0 )) # sorts thread_tabs as [all threads, 1, 2, 3 ....]
240
241
241
242
for thread_tab in thread_tabs
242
243
gdict_thread = gdict[thread_tab]
243
244
task_tabs = collect (keys (gdict_thread))
244
245
sort! (task_tabs, by = s -> s == tabname_alltasks ? " " : string (s)) # sorts thread_tabs as [all threads, 0xds ....]
245
246
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 )
249
250
task_tab_num = 1
250
251
for task_tab in task_tabs
251
252
g = gdict_thread[task_tab]
252
253
gsig = Observable (g) # allow substitution by the open dialog
253
254
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
276
276
277
277
push! (tb, tb_open)
278
278
push! (tb, tb_save_as)
279
- push! (tb, SeparatorToolItem ( ))
279
+ push! (tb, GtkSeparator ( :h ))
280
280
push! (tb, tb_zoom_fit)
281
281
push! (tb, tb_zoom_out)
282
282
push! (tb, tb_zoom_in)
283
- push! (tb, SeparatorToolItem ( ))
283
+ push! (tb, GtkSeparator ( :h ))
284
284
push! (tb, tb_info)
285
- push! (tb, SeparatorToolItem ( ))
286
- push! (tb, tb_text_item )
285
+ push! (tb, GtkSeparator ( :h ))
286
+ push! (tb, tb_text )
287
287
# FIXME : likely have to do `allkwargs` in the open/save below (add in C, combine, recur)
288
288
signal_connect (open_cb, tb_open, " clicked" , Nothing, (), false , (widget (c),gsig,kwargs))
289
289
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), ))
291
291
292
- bx = Box (:v )
292
+ bx = GtkBox (:v )
293
293
push! (bx, tb)
294
294
push! (bx, f)
295
295
# 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 ))
297
297
fdraw = viewprof (fcolor, c, gsig, (tb_zoom_fit, tb_zoom_out, tb_zoom_in, tb_text), graphtype; kwargs... )
298
298
GtkObservables. gc_preserve (nb_threads, c)
299
299
GtkObservables. gc_preserve (nb_threads, fdraw)
@@ -303,12 +303,12 @@ function viewgui(fcolor, gdict::NestedGraphDict; data=nothing, lidict=nothing, w
303
303
push! (nb_threads, nb_tasks, string (thread_tab))
304
304
end
305
305
306
- bx = Box (:v )
306
+ bx = GtkBox (:v )
307
307
push! (bx, nb_threads)
308
308
309
309
# 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
312
312
313
313
# Register the window with closeall
314
314
window_wrefs[win] = nothing
@@ -317,12 +317,8 @@ function viewgui(fcolor, gdict::NestedGraphDict; data=nothing, lidict=nothing, w
317
317
end
318
318
319
319
# 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))
326
322
327
323
return win, _c, _fdraw, (_tb_open, _tb_save_as)
328
324
end
@@ -369,7 +365,7 @@ function viewprof_func(fcolor, c, g, fontsize, tb_items, graphtype)
369
365
img, tagimg = img[:,2 : end ], discardfirstcol (tagimg)
370
366
img24 = RGB24 .(img)
371
367
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 )
373
369
zr = Observable (ZoomRegion (fv, fv))
374
370
signal_connect (zoom_fit_cb, tb_zoom_fit, " clicked" , Nothing, (), false , (zr))
375
371
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)
393
389
lasttextbb = Ref (BoundingBox (1 ,0 ,1 ,0 ))
394
390
sigmotion = on (c. mouse. motion) do btn
395
391
# Repair image from ovewritten text
396
- if c. widget. is_realized && c . widget . is_sized
392
+ if c. widget. is_sized
397
393
ctx = getgc (c)
398
394
if Graphics. width (lasttextbb[]) > 0
399
395
r = zr[]
@@ -410,17 +406,18 @@ function viewprof_func(fcolor, c, g, fontsize, tb_items, graphtype)
410
406
# Write the info
411
407
xu, yu = btn. position. x, btn. position. y
412
408
sf = gettag (tagimg, xu, yu)
409
+ b = Gtk4. buffer (tb_text)
413
410
if sf != StackTraces. UNKNOWN
414
411
str_long = long_info_str (sf)
415
- Gtk . GAccessor . text (tb_text, str_long)
412
+ b[String] = str_long
416
413
str = string (basename (string (sf. file)), " , " , sf. func, " : line " , sf. line)
417
414
set_source (ctx, fcolor (:font ))
418
415
Cairo. set_font_face (ctx, " sans-serif $(fontsize) px" )
419
416
xi = zr[]. currentview. x
420
417
xmin, xmax = minimum (xi), maximum (xi)
421
418
lasttextbb[] = deform (Cairo. text (ctx, xu, yu, str, halign = xu < (2 xmin+ xmax)/ 3 ? " left" : xu < (xmin+ 2 xmax)/ 3 ? " center" : " right" ), - 2 , 2 , - 2 , 2 )
422
419
else
423
- Gtk . GAccessor . text (tb_text, " " )
420
+ b[String] = " "
424
421
end
425
422
reveal (c)
426
423
end
466
463
467
464
@guarded function open_cb (:: Ptr , settings:: Tuple )
468
465
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
472
471
end
473
472
474
473
function _open (gsig, selection; kwargs... )
@@ -484,12 +483,15 @@ end
484
483
485
484
@guarded function save_as_cb (:: Ptr , profdata:: Tuple )
486
485
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
491
493
end
492
- return _save (selection, data, lidict)
494
+ return nothing
493
495
end
494
496
495
497
function _save (selection, args... )
512
514
return nothing
513
515
end
514
516
515
- @guarded function info_cb (:: Ptr , :: Tuple )
517
+ @guarded function info_cb (:: Ptr , win :: Tuple )
516
518
# Note: Keep this updated with the readme
517
519
info = """
518
520
ProfileView.jl Interface Tips
@@ -542,10 +544,22 @@ end
542
544
543
545
Color theme: The color theme used for the graph is `:light`, which can be changed to `:dark` via `ProfileView.set_theme!(:dark)`
544
546
"""
545
- info_dialog (info)
547
+ info_dialog (info, toplevel (win[1 ])) do
548
+ nothing
549
+ end
546
550
return nothing
547
551
end
548
552
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
+
549
563
discardfirstcol (A) = A[:,2 : end ]
550
564
discardfirstcol (A:: IndirectArray ) = IndirectArray (A. index[:,2 : end ], A. values)
551
565
@@ -577,6 +591,12 @@ function __init__()
577
591
printstyled (io, " \n `using Cthulhu` is required for `$(exc. f) `" ; color= :yellow )
578
592
end
579
593
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)
580
600
end
581
601
582
602
using PrecompileTools
@@ -607,12 +627,12 @@ let
607
627
win, c, fdraw = viewgui (FlameGraphs. default_colors, gdict)
608
628
for obs in c. preserved
609
629
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)
611
632
end
612
633
end
613
634
precompile (fdraw)
614
635
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
616
636
end
617
637
end
618
638
end
0 commit comments