diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 00861ae..1cc279b 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -15,7 +15,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest, windows-latest, macOS-latest] - version: ['1.3', '1.6', '1', 'nightly'] + version: ['1.6', '1', 'nightly'] arch: [x64, x86] include: - os: ubuntu-latest @@ -23,15 +23,13 @@ jobs: exclude: - os: macOS-latest arch: x86 - - version: '1.3' - arch: x86 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: julia-actions/setup-julia@v1 with: version: ${{ matrix.version }} arch: ${{ matrix.arch }} - - uses: actions/cache@v1 + - uses: actions/cache@v3 env: cache-name: cache-artifacts with: diff --git a/.github/workflows/Documenter.yml b/.github/workflows/Documenter.yml index 73fc3e3..e4061ac 100644 --- a/.github/workflows/Documenter.yml +++ b/.github/workflows/Documenter.yml @@ -10,7 +10,7 @@ jobs: name: Documentation runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: julia-actions/julia-buildpkg@latest - uses: julia-actions/julia-docdeploy@v1 with: diff --git a/Project.toml b/Project.toml index 4a256a7..6772964 100644 --- a/Project.toml +++ b/Project.toml @@ -8,7 +8,7 @@ Colors = "5ae59095-9a9b-59fe-a467-6f913c188581" Dates = "ade2ca70-3891-5945-98fb-dc099432e06a" FixedPointNumbers = "53c48c17-4a7d-5ca2-90c5-79b7896eea93" Graphics = "a2bd30eb-e257-5431-a919-1863eab51364" -Gtk = "4c0ca9eb-093a-5379-98c5-f87ac0bbbf44" +Gtk4 = "9db2cae5-386f-4011-9d63-a5602296539b" IntervalSets = "8197267c-284f-5f27-9208-e0e47529a953" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" Observables = "510215fc-4207-5dde-b226-833fc4488ee2" @@ -21,13 +21,13 @@ Cairo = "1" Colors = "0.12" FixedPointNumbers = "0.8" Graphics = "1" -Gtk = "1" +Gtk4 = "0.5" IntervalSets = "0.5, 0.6, 0.7" Observables = "0.4, 0.5" PrecompileTools = "1" Reexport = "0.2, 1" RoundingIntegers = "0.2, 1" -julia = "1.3" +julia = "1.6" [extras] Cairo = "159f3aea-2a34-519c-b102-8c37f9878175" diff --git a/docs/Project.toml b/docs/Project.toml index 0fb1a47..70dd65c 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -2,7 +2,7 @@ Colors = "5ae59095-9a9b-59fe-a467-6f913c188581" Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" Graphics = "a2bd30eb-e257-5431-a919-1863eab51364" -Gtk = "4c0ca9eb-093a-5379-98c5-f87ac0bbbf44" +Gtk4 = "9db2cae5-386f-4011-9d63-a5602296539b" ImageMagick = "6218d12a-5da1-5696-b52f-db25d2ecc6d1" TestImages = "5e47fb64-e119-507b-a336-dd2b206d9990" diff --git a/docs/src/controls.md b/docs/src/controls.md index 5e07411..2640640 100644 --- a/docs/src/controls.md +++ b/docs/src/controls.md @@ -2,11 +2,10 @@ Let's create a `slider` object: ```jldoctest demo1 -julia> using Gtk.ShortNames, GtkObservables +julia> using Gtk4, GtkObservables julia> sl = slider(1:11) -Gtk.GtkScaleLeaf with Observable{Int64} with 1 listeners. Value: -6 +GtkScaleLeaf with Observable(6) julia> typeof(sl) GtkObservables.Slider{Int64} @@ -18,11 +17,10 @@ the on-screen display). We can extract both of these components: ```jldoctest demo1 julia> observable(sl) -Observable{Int64} with 1 listeners. Value: -6 +Observable(6) julia> typeof(widget(sl)) -Gtk.GtkScaleLeaf +GtkScaleLeaf ``` (If you omitted the `typeof`, you'd instead see a long display that encodes the settings of the `GtkScaleLeaf` widget.) @@ -30,18 +28,15 @@ At present, this slider is not affiliated with any window. Let's create one and add the slider to the window. We'll put it inside a `Box` so that we can later add more things to this GUI (this illustrates usage of some of -[Gtk's layout tools](http://juliagraphics.github.io/Gtk.jl/latest/manual/layout.html): +[Gtk's layout tools](https://juliagtk.github.io/Gtk4.jl/dev/manual/layout/): ```jldoctest demo1 -julia> win = Window("Testing") |> (bx = Box(:v)); # a window containing a vertical Box for layout +julia> win = GtkWindow("Testing"); win[] = bx = GtkBox(:v); # a window containing a vertical Box for layout julia> push!(bx, sl); # put the slider in the box, shorthand for push!(bx, widget(sl)) - -julia> Gtk.showall(win); ``` -Because of the `showall`, you should now see a window with your slider -in it: +You should now see a window with your slider in it: ![slider1](assets/slider1.png) @@ -50,13 +45,14 @@ that we used to create `sl`. Now drag the slider all the way to the right, and then see what happened to `sl`: ```@meta -sl[] = 11 # Updates the value of a Observable. See the Observables.jl docs. +DocTestSetup = quote + sl[] = 11 # Updates the value of a Observable. See the Observables.jl docs. +end ``` ```jldoctest demo1 julia> sl -Gtk.GtkScaleLeaf with Observable{Int64} with 1 listeners. Value: -6 +GtkScaleLeaf with Observable(11) ``` You can see that dragging the slider caused the value of the observable to @@ -76,12 +72,10 @@ value into a textbox: ```jldoctest demo1 julia> tb = textbox(Int; observable=observable(sl)) -Gtk.GtkEntryLeaf with Observable{Int64} with 2 listeners. Value: +GtkEntryLeaf with Observable{Int64} with 2 listeners. Value: 1 julia> push!(bx, tb); - -julia> Gtk.showall(win); ``` ![slider2](assets/slider2.png) diff --git a/docs/src/drawing.md b/docs/src/drawing.md index abf012c..958d81c 100644 --- a/docs/src/drawing.md +++ b/docs/src/drawing.md @@ -9,10 +9,9 @@ Let's begin by creating a window with a canvas in it: ```julia using GtkObservables, Colors -using GtkObservables.Gtk -using GtkObservables.Gtk.ShortNames +using GtkObservables.Gtk4 -win = Window("Drawing") +win = GtkWindow("Drawing") c = canvas(UserUnit) # create a canvas with user-specified coordinates push!(win, c) ``` @@ -34,7 +33,7 @@ We're going to set this up so that a new line is started when the user clicks with the left mouse button; when the user releases the mouse button, the line is finished and added to a list of previously-drawn lines. Consequently, we need a place to store user data. We'll use -Signals, so that our Canvas will be notified when there is new +Observables, so that our Canvas will be notified when there is new material to draw: ```julia @@ -116,9 +115,9 @@ by monitoring `lines` from the command line by clicking, dragging, and releasing. However, it's much more fun to see it in action. Let's set up a -[`draw`](http://juliagraphics.github.io/Gtk.jl/latest/manual/canvas.html) +[`draw`](http://juliagtk.github.io/Gtk4.jl/dev/manual/canvas.html) method for the canvas, which will be called (1) whenever the window -resizes (this is arranged by Gtk.jl), or (2) whenever `lines` or +resizes (this is arranged by Gtk4.jl), or (2) whenever `lines` or `newline` update (because we supply them as arguments to the `draw` function): @@ -127,7 +126,7 @@ function): redraw = draw(c, lines, newline) do cnvs, lns, newl # the function body takes 3 arguments fill!(cnvs, colorant"white") # set the background to white set_coordinates(cnvs, BoundingBox(0, 1, 0, 1)) # set coordinates to 0..1 along each axis - ctx = Gtk.getgc(cnvs) # gets the "graphics context" object (see Cairo/Gtk) + ctx = Gtk4.getgc(cnvs) # gets the "graphics context" object (see Cairo/Gtk) for l in lns drawline(ctx, l, colorant"blue") # draw old lines in blue end @@ -148,13 +147,13 @@ end ``` **Important note:** Only modify the canvas inside the `draw` function, and pass -all observables that you want to consume as additional arguments (the example shows +all observables that you want to consume as additional arguments (the example shows three, but you may pass as few or as many as you wish). Otherwise, you may find the rendering behaves unexpectedly. A lot of these commands come from Cairo.jl and/or Graphics.jl. -Our application is done! (But don't forget to `showall(win)`.) Here's a +Our application is done! Here's a picture of me in the middle of a very fancy drawing: ![drawing](assets/drawing.png) diff --git a/docs/src/index.md b/docs/src/index.md index d831723..af2dc91 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -3,7 +3,7 @@ ## Scope of this package GtkObservables is a package building on the functionality of -[Gtk.jl](https://github.com/JuliaGraphics/Gtk.jl) and +[Gtk4.jl](https://github.com/JuliaGtk/Gtk4.jl) and [Observables.jl](https://github.com/JuliaGizmos/Observables.jl). Its main purpose is to simplify the handling of interactions among components of a graphical user interface (GUI). @@ -17,24 +17,24 @@ Creating a GUI generally involves some or all of the following: 5. (for graphical applications) canvas interaction (mouse clicks, drags, etc.) GtkObservables is targeted primarily at items 1, 3, and 5. Layout is -handled by Gtk.jl, and drawing (with a couple of exceptions) is +handled by Gtk4.jl, and drawing (with a couple of exceptions) is handled by plotting packages or at a lower level by [Cairo](https://github.com/JuliaGraphics/Cairo.jl). GtkObservables is suitable for: - "quick and dirty" applications which you might create from the command line -- more sophisticated GUIs where layout is specified using tools like [Glade](https://glade.gnome.org/) +- more sophisticated GUIs where layout is specified using tools like GtkBuilder XML. -For usage with Glade, the [Input widgets](@ref) and +For usage with GtkBuilder XML, the [Input widgets](@ref) and [Output widgets](@ref) defined by this package allow you to supply a preexisting `widget` (which you might load with GtkBuilder) rather than creating one from scratch. Users interested in using GtkObservables -with Glade are encouraged to see how the [`player`](@ref) widget is +this way are encouraged to see how the [`player`](@ref) widget is constructed (see `src/extrawidgets.jl`). At present, GtkObservables supports only a small subset of the -[widgets provided by Gtk](https://developer.gnome.org/gtk3/stable/ch03.html). It +[widgets provided by Gtk4](https://docs.gtk.org/gtk4/#classes). It is fairly straightforward to add new ones, and pull requests would be welcome. @@ -44,24 +44,24 @@ The central concept of Observables.jl is the `Observable`, a type that allows updating with new values that then triggers actions that may update other `Observable`s or execute functions. Your GUI ends up being represented as a "graph" of Observables that collectively propagate the -state of your GUI. GtkObservables couples `Observable`s to Gtk.jl's +state of your GUI. GtkObservables couples `Observable`s to Gtk4.jl's widgets. In essence, Observables.jl allows ordinary Julia objects to become the triggers for callback actions; the primary advantage of -using Julia objects, rather than Gtk widgets, as the "application +using Julia objects, rather than Gtk4 widgets, as the "application logic" triggers is that it simplifies reasoning about the GUI and seems to reduce the number of times ones needs to consult the -[Gtk documentation](https://developer.gnome.org/gtk3/stable/gtkobjects.html). +[Gtk documentation](https://docs.gtk.org/gtk4). Please see the [Observables.jl documentation](https://juliagizmos.github.io/Observables.jl/stable/) for more information. ## Important note: The UI thread Changes to the UI can only be performed safely if the code is run in the same -thread that the Gtk main loop is running on. `Observable` handlers may run +thread that the Gtk4 main loop is running on. `Observable` handlers may run on a different thread. If a UI update operation occurs on a different thread, the process (Julia) can crash. The solution is to wrap the offending code block -with `Gtk.@idle_add`, which requests Gtk to run the code block on the UI thread -when the CPU is idle. However, this also means that code inside the block will +with `Gtk4.GLib.@idle_add`, which requests Gtk4 to run the code block on the UI thread +when the main loop is idle. However, this also means that code inside the block will not run synchronously with code outside it. ```julia on(myobs) do val diff --git a/docs/src/zoom_pan.md b/docs/src/zoom_pan.md index 8aa7eda..f78bb62 100644 --- a/docs/src/zoom_pan.md +++ b/docs/src/zoom_pan.md @@ -7,9 +7,9 @@ and zoom functionality. To illustrate these tools, let's first open a window with a drawing canvas: ```jldoctest demozoom -julia> using Gtk.ShortNames, GtkObservables, TestImages +julia> using Gtk4, GtkObservables, TestImages -julia> win = Window("Image"); +julia> win = GtkWindow("Image"); julia> c = canvas(UserUnit); @@ -77,9 +77,6 @@ by assigning it to a variable we ensure it won't be garbage-collected `zr` update). Now, let's see our image: -```jldoctest demozoom -julia> Gtk.showall(win); -``` ![image1](assets/image1.png) diff --git a/examples/drawing.jl b/examples/drawing.jl index 282ad45..2934783 100644 --- a/examples/drawing.jl +++ b/examples/drawing.jl @@ -1,6 +1,6 @@ -using Gtk.ShortNames, GtkObservables, Graphics, Colors +using Gtk4, GtkObservables, Colors, Observables -win = Window("Drawing") +win = GtkWindow("Drawing") c = canvas(UserUnit) # create a canvas with user-specified coordinates push!(win, c) @@ -55,5 +55,3 @@ function drawline(ctx, l, color) end stroke(ctx) end - -Gtk.showall(win) diff --git a/examples/imageviewer.jl b/examples/imageviewer.jl index 3053673..e4ecfa4 100644 --- a/examples/imageviewer.jl +++ b/examples/imageviewer.jl @@ -1,4 +1,4 @@ -using TestImages, GtkObservables, Gtk.ShortNames +using TestImages, GtkObservables, Gtk4 img = testimage("lighthouse") @@ -6,7 +6,7 @@ img = testimage("lighthouse") # UserUnit: we'll use the indices of the image as canvas coordinates # This makes it easy to relate the position of mouse-clicks to the image c = canvas(UserUnit) -win = Window(c) +win = GtkWindow(c) ## Region-of-interest selection zr = Observable(ZoomRegion(img)) @@ -33,6 +33,3 @@ redraw = draw(c, imgroi) do cnvs, image # zoom in further, we select the correct region. set_coordinates(cnvs, zr[]) end - -## Don't forget this! -Gtk.showall(win) diff --git a/examples/widgets.jl b/examples/widgets.jl index a1693c2..968426d 100644 --- a/examples/widgets.jl +++ b/examples/widgets.jl @@ -1,4 +1,4 @@ -using GtkObservables, Gtk.ShortNames, Colors +using GtkObservables, Gtk4, Colors # Create some controls n = slider(1:10) @@ -17,22 +17,23 @@ cbhandle = on(dd.mappedsignal) do cb # assign to variable to prevent garbage co cb() end -# Lay out the GUI. You can alternatively use `glade` and pass the +# Lay out the GUI. You can alternatively use `GtkBuilder` XML and pass the # widgets to the constructors above (see the implementation of # `player` in `extrawidgets.jl` for an example). -mainwin = Window("GtkObservables") -vbox = Box(:v) -hbox = Box(:h) +mainwin = GtkWindow("GtkObservables", 300, 200) +vbox = GtkBox(:v) +hbox = GtkBox(:h) push!(vbox, hbox) push!(hbox, n) push!(hbox, tb) push!(vbox, dd) push!(vbox, cb) push!(mainwin, vbox) +show(mainwin) # Create the auxillary window and link its visibility to the checkbox cnvs = canvas() -auxwin = Window(cnvs) +auxwin = GtkWindow(cnvs) showwin = map(cb) do val set_gtk_property!(auxwin, "visible", val) end @@ -44,6 +45,3 @@ end draw(cnvs) do c fill!(c, colorant"orange") end -Gtk.showall(auxwin) - -Gtk.showall(mainwin) diff --git a/src/GtkObservables.jl b/src/GtkObservables.jl index 3b2c4ef..31fe66f 100644 --- a/src/GtkObservables.jl +++ b/src/GtkObservables.jl @@ -5,7 +5,7 @@ if isdefined(Base, :Experimental) && isdefined(Base.Experimental, Symbol("@compi end using LinearAlgebra # for `inv` -using Gtk, Colors, FixedPointNumbers, Reexport +using Gtk4, Colors, FixedPointNumbers, Reexport @reexport using Observables using Graphics using Graphics: set_coordinates, BoundingBox @@ -13,20 +13,25 @@ using IntervalSets, RoundingIntegers # There's a conflict for width, so we have to scope those calls import Cairo -using Gtk: GtkWidget +using Gtk4: GtkScaleLeaf, GtkCheckButtonLeaf, GtkToggleButtonLeaf, + GtkButtonLeaf, GtkSpinButtonLeaf, GtkColorButtonLeaf, + GtkEntryLeaf, GtkTextViewLeaf, GtkComboBoxTextLeaf, + GtkLabelLeaf, GtkProgressBarLeaf # Constants for event analysis -using Gtk.GConstants.GdkModifierType: SHIFT, CONTROL, MOD1 -using Gtk.GConstants.GdkScrollDirection: UP, DOWN, LEFT, RIGHT -using Gtk.GdkEventType: BUTTON_PRESS, DOUBLE_BUTTON_PRESS, BUTTON_RELEASE - -if !isdefined(Gtk, :signal_handler_is_connected) - signal_handler_is_connected(w::GObject, handler_id::Culong) = - ccall((:g_signal_handler_is_connected, Gtk.libgobject), Cint, (Ptr{GObject}, Culong), w, handler_id) == 1 -end +const SHIFT = Gtk4.ModifierType_SHIFT_MASK +const CONTROL = Gtk4.ModifierType_CONTROL_MASK +const MOD1 = Gtk4.ModifierType_ALT_MASK +const BUTTON_PRESS = Gtk4.EventType_BUTTON_PRESS +const BUTTON_RELEASE = Gtk4.EventType_BUTTON_RELEASE +const MOTION_NOTIFY = Gtk4.EventType_MOTION_NOTIFY +const UP = Gtk4.ScrollDirection_UP +const DOWN = Gtk4.ScrollDirection_DOWN +const LEFT = Gtk4.ScrollDirection_LEFT +const RIGHT = Gtk4.ScrollDirection_RIGHT # Re-exports export set_coordinates, BoundingBox, SHIFT, CONTROL, MOD1, UP, DOWN, LEFT, RIGHT, - BUTTON_PRESS, DOUBLE_BUTTON_PRESS, destroy + BUTTON_PRESS, destroy ## Exports export slider, button, checkbox, togglebutton, colorbutton, dropdown, textbox, textarea, spinbutton, cyclicspinbutton, progressbar @@ -57,10 +62,9 @@ observable(x::Observable) = x Return the GtkWidget `gtkw` associated with widget `w`. """ -widget(w::Widget) = w.widget +Gtk4.widget(w::Widget) = w.widget Base.show(io::IO, w::Widget) = print(io, typeof(widget(w)), " with ", observable(w)) -Gtk.destroy(w::Widget) = destroy(widget(w)) Base.getindex(w::Widget) = getindex(observable(w)) Base.setindex!(w::Widget, val) = setindex!(observable(w), val) Observables.on(f, w::Widget; kwargs...) = on(f, observable(w); kwargs...) @@ -87,26 +91,26 @@ include("rubberband.jl") ## More convenience functions # Containers -Gtk.GtkWindow(w::Union{Widget,Canvas}) = GtkWindow(widget(w)) -Gtk.GtkFrame(w::Union{Widget,Canvas}) = GtkFrame(widget(w)) -Gtk.GtkAspectFrame(w::Union{Widget,Canvas}, args...) = +Gtk4.GtkWindow(w::Union{Widget,Canvas}) = GtkWindow(widget(w)) +Gtk4.GtkFrame(w::Union{Widget,Canvas}) = GtkFrame(widget(w)) +Gtk4.GtkAspectFrame(w::Union{Widget,Canvas}, args...) = GtkAspectFrame(widget(w), args...) -Base.push!(container::Union{Gtk.GtkBin,GtkBox}, child::Widget) = +Base.push!(container::Union{GtkWindow,GtkBox}, child::Widget) = push!(container, widget(child)) -Base.push!(container::Union{Gtk.GtkBin,GtkBox}, child::Canvas) = +Base.push!(container::Union{GtkWindow,GtkBox}, child::Canvas) = push!(container, widget(child)) -Base.:|>(parent::Gtk.GtkContainer, child::Union{Widget,Canvas}) = push!(parent, child) +Base.:|>(parent::Gtk4.GtkBox, child::Union{Widget,Canvas}) = push!(parent, child) -widget(c::Canvas) = c.widget +Gtk4.widget(c::Canvas) = c.widget -Gtk.set_gtk_property!(w::Union{Widget,Canvas}, key, val) = set_gtk_property!(widget(w), key, val) -Gtk.get_gtk_property(w::Union{Widget,Canvas}, key) = get_gtk_property(widget(w), key) -Gtk.get_gtk_property(w::Union{Widget,Canvas}, key, ::Type{T}) where {T} = get_gtk_property(widget(w), key, T) +Gtk4.set_gtk_property!(w::Union{Widget,Canvas}, key, val) = set_gtk_property!(widget(w), key, val) +Gtk4.get_gtk_property(w::Union{Widget,Canvas}, key) = get_gtk_property(widget(w), key) +Gtk4.get_gtk_property(w::Union{Widget,Canvas}, key, ::Type{T}) where {T} = get_gtk_property(widget(w), key, T) -Base.unsafe_convert(::Type{Ptr{Gtk.GLib.GObject}}, w::Union{Widget,Canvas}) = - Base.unsafe_convert(Ptr{Gtk.GLib.GObject}, widget(w)) +Base.unsafe_convert(::Type{Ptr{Gtk4.GLib.GObject}}, w::Union{Widget,Canvas}) = + Base.unsafe_convert(Ptr{Gtk4.GLib.GObject}, widget(w)) Graphics.getgc(c::Canvas) = getgc(c.widget) Graphics.width(c::Canvas) = Graphics.width(c.widget) @@ -143,7 +147,7 @@ function Base.setindex!(zr::Observable{ZoomRegion{T}}, inds::Tuple{AbstractUnitR setindex!(zr, ClosedInterval{T}.(inds)) end -Gtk.reveal(c::Canvas, args...) = reveal(c.widget, args...) +Gtk4.reveal(c::Canvas, args...) = reveal(c.widget, args...) const _ref_dict = IdDict{Any, Any}() @@ -159,6 +163,6 @@ function gc_preserve(widget::Union{GtkWidget,GtkCanvas}, obj) end end -Base.VERSION >= v"1.4.2" && include("precompile.jl") +include("precompile.jl") end # module diff --git a/src/datetime.glade b/src/datetime.glade index 1eb6c78..7ceae7c 100644 --- a/src/datetime.glade +++ b/src/datetime.glade @@ -1,150 +1,86 @@ - - + + - True - False - 0 - none - + 0 + - True - False + 0 - True - True + 0 + 1 vertical - - False - True - 0 - - True - False + 0 - - - False - True - 1 - - True - True + 0 + 1 vertical - - False - True - 2 - - True - False + 0 - - - False - True - 3 - - True - True + 0 + 1 vertical - - False - True - 4 - - True - False + 0 - - False - True - 5 - - True - True + 0 + 1 vertical - - False - True - 6 - - True - False + 0 : - - False - True - 7 - - True - True + 0 + 1 vertical - - False - True - 8 - - True - False + 0 : - - False - True - 9 - - True - True + 0 + 1 vertical - - False - True - 10 - - + diff --git a/src/extrawidgets.jl b/src/extrawidgets.jl index b944581..8b774e1 100644 --- a/src/extrawidgets.jl +++ b/src/extrawidgets.jl @@ -44,16 +44,16 @@ end frame(p::PlayerWithTextbox) = p.frame function PlayerWithTextbox(builder, index::Observable{Int}, range::UnitRange{Int}, id::Int=1) - 1 <= id <= 2 || error("only 2 player widgets are defined in player.glade") + 1 <= id <= 2 || error("only 2 player widgets are defined in player.ui") direction = Observable(Int8(0)) - frame = Gtk.G_.object(builder,"player_frame$id")::Gtk.GtkFrameLeaf - scale = slider(range; widget=Gtk.G_.object(builder,"index_scale$id")::Gtk.GtkScaleLeaf, observable=index) - entry = textbox(first(range); widget=Gtk.G_.object(builder,"index_entry$id")::Gtk.GtkEntryLeaf, observable=index, range=range) - play_back = button(; widget=Gtk.G_.object(builder,"play_back$id")::Gtk.GtkButtonLeaf) - step_back = button(; widget=Gtk.G_.object(builder,"step_back$id")::Gtk.GtkButtonLeaf) - stop = button(; widget=Gtk.G_.object(builder,"stop$id")::Gtk.GtkButtonLeaf) - step_forward = button(; widget=Gtk.G_.object(builder,"step_forward$id")::Gtk.GtkButtonLeaf) - play_forward = button(; widget=Gtk.G_.object(builder,"play_forward$id")::Gtk.GtkButtonLeaf) + frame = builder["player_frame$id"]::Gtk4.GtkFrame + scale = slider(range; widget=builder["index_scale$id"]::Gtk4.GtkScale, observable=index) + entry = textbox(first(range); widget=builder["index_entry$id"]::Gtk4.GtkEntry, observable=index, range=range) + play_back = button(; widget=builder["play_back$id"]::Gtk4.GtkButton) + step_back = button(; widget=builder["step_back$id"]::Gtk4.GtkButton) + stop = button(; widget=builder["stop$id"]::Gtk4.GtkButton) + step_forward = button(; widget=builder["step_forward$id"]::Gtk4.GtkButton) + play_forward = button(; widget=builder["play_forward$id"]::Gtk4.GtkButton) # Fix up widget properties set_gtk_property!(scale.widget, "round-digits", 0) # glade/gtkbuilder bug that I have to set this here? @@ -96,7 +96,7 @@ function PlayerWithTextbox(builder, index::Observable{Int}, range::UnitRange{Int PlayerWithTextbox(range, direction, frame, scale, entry, play_back, step_back, stop, step_forward, play_forward), preserved end function PlayerWithTextbox(index::Observable{Int}, range::AbstractUnitRange{<:Integer}, id::Integer=1) - builder = GtkBuilder(filename=joinpath(splitdir(@__FILE__)[1], "player.glade")) + builder = GtkBuilder(joinpath(splitdir(@__FILE__)[1], "player.ui")) PlayerWithTextbox(builder, index, convert(UnitRange{Int}, range), convert(Int, id)) end @@ -123,20 +123,8 @@ function player(cs::Observable, range::AbstractUnitRange{<:Integer}; style="with error("style $style not recognized") end -Base.unsafe_convert(::Type{Ptr{Gtk.GLib.GObject}}, p::PlayerWithTextbox) = - Base.unsafe_convert(Ptr{Gtk.GLib.GObject}, frame(p)) - -function Gtk.destroy(p::PlayerWithTextbox) - destroy(p.play_back) - destroy(p.step_back) - destroy(p.stop) - destroy(p.step_forward) - destroy(p.play_forward) - destroy(p.entry) - destroy(p.scale) - destroy(frame(p)) -end - +Base.unsafe_convert(::Type{Ptr{Gtk4.GLib.GObject}}, p::PlayerWithTextbox) = + Base.unsafe_convert(Ptr{Gtk4.GLib.GObject}, frame(p)) ################# A time widget ########################## @@ -149,11 +137,11 @@ end timewidget(time) Return a time widget that includes the `Time` and a `GtkFrame` with the hour, minute, and -second widgets in it. You can specify the specific `GtkFrame` widget (useful when using the `Gtk.Builder` and `glade`). Time is guaranteed to be positive. +second widgets in it. You can specify the specific `GtkFrame` widget (useful when using `GtkBuilder`). Time is guaranteed to be positive. """ function timewidget(t1::Dates.Time; widget=nothing, observable=nothing) zerotime = Dates.Time(0,0,0) # convenient since we'll use it frequently - b = Gtk.GtkBuilder(filename=joinpath(@__DIR__, "time.glade")) + b = Gtk4.GtkBuilder(joinpath(@__DIR__, "time.glade")) if observable === nothing observable = Observable(t1) # this is the input observable, we can push! into it to update the widget end @@ -170,7 +158,7 @@ function timewidget(t1::Dates.Time; widget=nothing, observable=nothing) end t2 = map(last, H) # here is the final time connect_nofire!(observable, t2) # we connect the input and output times so that any update to the resulting time will go into the input observable and actually show on the widgets - Sint = Observable(Dates.value(first(S[]))) # necessary for now, until range-like GtkObservables.widgets can accept other ranges. + Sint = Observable(Dates.value(first(S[]))) # necessary for now, until range-like Gtk4Observables.widgets can accept other ranges. Ssb = spinbutton(-1:60, widget=b["second"], observable=Sint) # allow for values outside the actual range of seconds so that we'll be able to increase and decrease minutes. on(Sint; weak=true) do x Δ = Dates.Second(x) - first(S[]) # how much did we change by, this should always be ±1 @@ -223,11 +211,11 @@ end Return a datetime widget that includes the `DateTime` and a `GtkBox` with the year, month, day, hour, minute, and second widgets in it. You can specify the specific `SpinButton` widgets for the hour, minute, and second (useful when using -`Gtk.Builder` and `glade`). Date and time are guaranteed to be positive. +`GtkBuilder`). Date and time are guaranteed to be positive. """ function datetimewidget(t1::DateTime; widget=nothing, observable=nothing) zerotime = DateTime(0,1,1,0,0,0) - b = Gtk.GtkBuilder(filename=joinpath(@__DIR__, "datetime.glade")) + b = Gtk4.GtkBuilder(joinpath(@__DIR__, "datetime.glade")) # the same logic is applied here as for `timewidget` if observable == nothing observable = Observable(t1) diff --git a/src/graphics_interaction.jl b/src/graphics_interaction.jl index 8800e7d..0586aeb 100644 --- a/src/graphics_interaction.jl +++ b/src/graphics_interaction.jl @@ -1,8 +1,5 @@ # Much of this is event-handling to support interactivity -using Gtk.GConstants: GDK_KEY_Left, GDK_KEY_Right, GDK_KEY_Up, GDK_KEY_Down -using Gtk.GConstants.GdkEventMask: KEY_PRESS, SCROLL - abstract type CairoUnit <: Real end Base.:+(x::U, y::U) where {U<:CairoUnit} = U(x.val + y.val) @@ -36,6 +33,7 @@ pixels). See the Cairo documentation. struct DeviceUnit <: CairoUnit val::Float64 end +DeviceUnit(x::DeviceUnit) = x """ UserUnit(x) @@ -92,8 +90,8 @@ end XY(x::T, y::T) where {T} = XY{T}(x, y) XY(x, y) = XY(promote(x, y)...) -function XY{U}(w::GtkCanvas, evt::Gtk.GdkEvent) where U<:CairoUnit - XY{U}(convertunits(U, w, DeviceUnit(evt.x), DeviceUnit(evt.y))...) +function XY{U}(w::GtkCanvas, x::Float64, y::Float64) where U<:CairoUnit + XY{U}(convertunits(U, w, DeviceUnit(x), DeviceUnit(y))...) end function Base.show(io::IO, xy::XY{T}) where T<:CairoUnit @@ -134,16 +132,36 @@ struct MouseButton{U<:CairoUnit} button::UInt32 clicktype::typeof(BUTTON_PRESS) modifiers::typeof(SHIFT) - gtkevent + n_press::Int32 end -function MouseButton(pos::XY{U}, button::Integer, clicktype::Integer, modifiers::Integer, gtkevent=nothing) where U - MouseButton{U}(pos, UInt32(button), oftype(BUTTON_PRESS, clicktype), oftype(SHIFT, modifiers), gtkevent) +function MouseButton(pos::XY{U}, button::Integer, clicktype, modifiers, n_press=1) where U<:CairoUnit + MouseButton{U}(pos, UInt32(button), clicktype, modifiers, n_press) end -function MouseButton{U}(w::GtkCanvas, evt::Gtk.GdkEvent) where U - MouseButton{U}(XY{U}(w, evt), evt.button, evt.event_type, evt.state, evt) + +function _get_button(modifiers, e::GtkEventController) + if modifiers & Gtk4.ModifierType_BUTTON1_MASK == Gtk4.ModifierType_BUTTON1_MASK + return 1 + elseif modifiers & Gtk4.ModifierType_BUTTON2_MASK == Gtk4.ModifierType_BUTTON2_MASK + return 2 + elseif modifiers & Gtk4.ModifierType_BUTTON3_MASK == Gtk4.ModifierType_BUTTON3_MASK + return 3 + else + return isa(e, GtkGestureSingle) ? Gtk4.current_button(e) : 0 + end end -function MouseButton{U}() where U - MouseButton(XY(U(-1), U(-1)), 0, 0, 0, nothing) + +function MouseButton{U}(e::GtkEventController, n_press::Integer, x::Float64, y::Float64, clicktype, modifier_ref=nothing) where U<:CairoUnit + modifiers = if modifier_ref === nothing + Gtk4.current_event_state(e) + else + modifier_ref[] + end + button = _get_button(modifiers,e) + w = widget(e) + MouseButton{U}(XY{U}(w, x, y), UInt32(button), clicktype, modifiers, n_press) +end +function MouseButton{U}() where U<:CairoUnit + MouseButton(XY(U(-1), U(-1)), UInt32(0), Gtk4.EventType(0), Gtk4.ModifierType(0)) end """ @@ -168,20 +186,28 @@ struct MouseScroll{U<:CairoUnit} direction::typeof(UP) modifiers::typeof(SHIFT) end -function MouseScroll(pos::XY{U}, direction::Integer, modifiers::Integer) where U - MouseScroll{U}(pos, oftype(UP, direction), oftype(SHIFT, modifiers)) +function MouseScroll(pos::XY{U}, direction, modifiers) where U <: CairoUnit + MouseScroll{U}(pos, direction, modifiers) end -function MouseScroll{U}(w::GtkCanvas, evt::Gtk.GdkEvent) where U - MouseScroll{U}(XY{U}(w, evt), evt.direction, evt.state) +function MouseScroll{U}(e::GtkEventController, direction, modifier_ref = nothing) where U <: CairoUnit + modifiers = if modifier_ref === nothing + Gtk4.current_event_state(e) + else + modifier_ref[] + end + evt = Gtk4.current_event(e) + b, x, y = if evt.handle != C_NULL + Gtk4.position(evt) + else + (false, 0.0, 0.0) + end + w = widget(e) + MouseScroll{U}(XY{U}(w, x, y), direction, modifiers) end function MouseScroll{U}() where U - MouseScroll(XY(U(-1), U(-1)), 0, 0) + MouseScroll(XY(U(-1), U(-1)), UP, Gtk4.ModifierType(0)) end -# immutable KeyEvent -# keyval -# end - """ MouseHandler{U<:CairoUnit} @@ -202,17 +228,46 @@ struct MouseHandler{U<:CairoUnit} ids::Vector{Culong} # for disabling any of these callbacks widget::GtkCanvas - function MouseHandler{U}(canvas::GtkCanvas) where U<:CairoUnit + function MouseHandler{U}(canvas::GtkCanvas, modifier_ref=nothing) where U<:CairoUnit pos = XY(U(-1), U(-1)) btn = MouseButton(pos, 0, BUTTON_PRESS, SHIFT) scroll = MouseScroll(pos, UP, SHIFT) ids = Vector{Culong}(undef, 0) handler = new{U}(Observable(btn), Observable(btn), Observable(btn), Observable(scroll), ids, canvas) # Create the callbacks - push!(ids, Gtk.on_signal_button_press(mousedown_cb, canvas, false, handler)) - push!(ids, Gtk.on_signal_button_release(mouseup_cb, canvas, false, handler)) - push!(ids, Gtk.on_signal_motion(mousemove_cb, canvas, 0, 0, false, handler)) - push!(ids, Gtk.on_signal_scroll(mousescroll_cb, canvas, false, handler)) + g = GtkGestureClick(canvas,0) + gm = GtkEventControllerMotion(canvas) + gs = GtkEventControllerScroll(Gtk4.EventControllerScrollFlags_VERTICAL, canvas) + + function mousedown_cb(ec::GtkGestureClick, n_press::Int32, x::Float64, y::Float64) + handler.buttonpress[] = MouseButton{U}(ec, n_press, x, y, BUTTON_PRESS, modifier_ref) + nothing + end + function mouseup_cb(ec::GtkGestureClick, n_press::Int32, x::Float64, y::Float64) + handler.buttonrelease[] = MouseButton{U}(ec, n_press, x, y, BUTTON_RELEASE, modifier_ref) + nothing + end + push!(ids, signal_connect(mousedown_cb, g, "pressed")) + push!(ids, signal_connect(mouseup_cb, g, "released")) + + function mousemove_cb(ec::GtkEventControllerMotion, x::Float64, y::Float64) + handler.motion[] = MouseButton{U}(ec, 0, x, y, MOTION_NOTIFY, modifier_ref) + nothing + end + push!(ids, signal_connect(mousemove_cb, gm, "motion")) + + function mousescroll_cb(ec::GtkEventControllerScroll, dx::Float64, dy::Float64) + vert = (abs(dy)>abs(dx)) + dir = if vert + dy > 0 ? Gtk4.ScrollDirection_UP : Gtk4.ScrollDirection_DOWN + else + dx > 0 ? Gtk4.ScrollDirection_RIGHT : Gtk4.ScrollDirection_LEFT + end + handler.scroll[] = MouseScroll{U}(ec, dir, modifier_ref) + Cint(1) + end + push!(ids, signal_connect(mousescroll_cb, gs, "scroll")) + handler end end @@ -221,7 +276,7 @@ end GtkObservables.Canvas{U}(w=-1, h=-1, own=true) Create a canvas for drawing and interaction. The relevant fields are: - - `canvas`: the "raw" Gtk widget (from Gtk.jl) + - `canvas`: the "raw" Gtk widget (from Gtk4.jl) - `mouse`: the [`MouseHandler{U}`](@ref) for this canvas. See also [`canvas`](@ref). @@ -231,25 +286,18 @@ struct Canvas{U} mouse::MouseHandler{U} preserved::Vector{Any} - function Canvas{U}(w::Int=-1, h::Int=-1; own::Bool=true) where U - gtkcanvas = GtkCanvas(w, h) - # Delete the Gtk handlers - for id in gtkcanvas.mouse.ids - signal_handler_disconnect(gtkcanvas, id) - end - empty!(gtkcanvas.mouse.ids) - # Initialize our own handlers - mouse = MouseHandler{U}(gtkcanvas) - set_gtk_property!(gtkcanvas, "is_focus", true) + function Canvas{U}(w::Int=-1, h::Int=-1; own::Bool=true, init_back=false, modifier_ref=nothing) where U + gtkcanvas = GtkCanvas(w, h, init_back) + # Initialize handlers + mouse = MouseHandler{U}(gtkcanvas, modifier_ref) + grab_focus(gtkcanvas) preserved = [] canvas = new{U}(gtkcanvas, mouse, preserved) gc_preserve(gtkcanvas, canvas) canvas end end -Canvas{U}(w::Integer, h::Integer=-1; own::Bool=true) where U = Canvas{U}(Int(w)::Int, Int(h)::Int; own=own) - -Gtk.destroy(c::Canvas) = destroy(c.widget) +Canvas{U}(w::Integer, h::Integer=-1; own::Bool=true, init_back = false, modifier_ref=nothing) where U = Canvas{U}(Int(w)::Int, Int(h)::Int; own=own, init_back=init_back, modifier_ref=modifier_ref) Base.show(io::IO, canvas::Canvas{U}) where U = print(io, "GtkObservables.Canvas{$U}()") @@ -261,8 +309,8 @@ width `w` and height `h`. `U` refers to the units for the canvas (for both drawing and reporting mouse pointer positions), see [`DeviceUnit`](@ref) and [`UserUnit`](@ref). See also [`GtkObservables.Canvas`](@ref). """ -canvas(::Type{U}=DeviceUnit, w::Integer=-1, h::Integer=-1) where {U<:CairoUnit} = Canvas{U}(w, h) -canvas(w::Integer, h::Integer) = canvas(DeviceUnit, w, h) +canvas(::Type{U}=DeviceUnit, w::Integer=-1, h::Integer=-1; init_back=false, modifier_ref=nothing) where {U<:CairoUnit} = Canvas{U}(w, h; init_back=init_back, modifier_ref=modifier_ref) +canvas(w::Integer, h::Integer; init_back=false, modifier_ref=nothing) = canvas(DeviceUnit, w, h; init_back=init_back, modifier_ref=modifier_ref) """ draw(f, c::GtkObservables.Canvas, signals...) @@ -289,7 +337,7 @@ using `do`-block notation: This would paint an image-Observable `imgobs` onto the canvas and then draw a red circle centered on `xsig`, `ysig`. """ -function Gtk.draw(drawfun::F, c::Canvas, signals::Observable...) where F +function Gtk4.draw(drawfun::F, c::Canvas, signals::Observable...) where F @guarded draw(c.widget) do widget # This used to have a `yield` in it to allow the Gtk event queue to run, # but that caused @@ -304,7 +352,7 @@ function Gtk.draw(drawfun::F, c::Canvas, signals::Observable...) where F push!(c.preserved, drawfunc) drawfunc end -function Gtk.draw(drawfun::F, c::Canvas, signal::Observable) where F +function Gtk4.draw(drawfun::F, c::Canvas, signal::Observable) where F @guarded draw(c.widget) do widget drawfun(widget, signal[]) end @@ -353,7 +401,7 @@ image_surface(img::AbstractArray{C}) where {C<:Colorant} = image_surface(convert(Matrix{ARGB32}, img)) -# Coordiantes could be AbstractFloat without an implied step, so let's +# Coordinates could be AbstractFloat without an implied step, so let's # use intervals instead of ranges struct ZoomRegion{T} fullview::XY{ClosedInterval{T}} @@ -499,8 +547,8 @@ You can flip the direction of either pan operation with `xpanflip` and """ function init_pan_scroll(canvas::Canvas{U}, zr::Observable{ZoomRegion{T}}, - @nospecialize(filter_x::Function) = evt->(evt.modifiers & 0x0f) == SHIFT || evt.direction == LEFT || evt.direction == RIGHT, - @nospecialize(filter_y::Function) = evt->(evt.modifiers & 0x0f) == 0 && (evt.direction == UP || evt.direction == DOWN), + @nospecialize(filter_x::Function) = evt->(evt.modifiers & SHIFT) == SHIFT || evt.direction == LEFT || evt.direction == RIGHT, + @nospecialize(filter_y::Function) = evt->(evt.modifiers & SHIFT) == 0 && (evt.direction == UP || evt.direction == DOWN), xpanflip = false, ypanflip = false) where {U,T} enabled = Observable(true) @@ -508,10 +556,8 @@ function init_pan_scroll(canvas::Canvas{U}, if enabled[] s = 0.1*scrollpm(event.direction) if filter_x(event) - # println("pan_x: ", event) setindex!(zr, pan_x(zr[], s)) elseif filter_y(event) - # println("pan_y: ", event) setindex!(zr, pan_y(zr[], s)) end end @@ -575,7 +621,7 @@ function init_pan_drag(canvas::Canvas{U}, end Dict{String,Any}("enabled"=>enabled, "active"=>active, "init"=>init, "drag"=>drag, "finish"=>finish) end -pandrag_button(btn) = btn.button == 1 && (btn.modifiers & 0x0f) == 0 +pandrag_button(btn) = btn.button == 1 && (btn.modifiers & CONTROL) == 0 pandrag_init_default(btn) = btn.clicktype == BUTTON_PRESS && pandrag_button(btn) """ @@ -608,7 +654,7 @@ zooming with `flip`. """ function init_zoom_scroll(canvas::Canvas{U}, zr::Observable{ZoomRegion{T}}, - @nospecialize(filter::Function) = evt->(evt.modifiers & 0x0f) == CONTROL, + @nospecialize(filter::Function) = evt->(evt.modifiers & CONTROL) == CONTROL, focus::Symbol = :pointer, factor = 2.0, flip = false) where {U,T} @@ -616,7 +662,6 @@ function init_zoom_scroll(canvas::Canvas{U}, enabled = Observable(true) zm = on(canvas.mouse.scroll; weak=true) do event::MouseScroll{U} if enabled[] && filter(event) - # println("zoom scroll: ", event) s = factor if event.direction == UP s = 1/s @@ -625,10 +670,8 @@ function init_zoom_scroll(canvas::Canvas{U}, s = 1/s end if focus === :pointer - # println("zoom focus: ", event) - setindex!(zr, zoom(zr[], s, event.position)) + setindex!(zr, zoom(zr[], s, canvas.mouse.motion[].position)) else - # println("zoom center: ", event) setindex!(zr, zoom(zr[], s)) end end @@ -636,41 +679,8 @@ function init_zoom_scroll(canvas::Canvas{U}, Dict{String,Any}("enabled"=>enabled, "zoom"=>zm) end -scrollpm(direction::Integer) = +scrollpm(direction) = direction == UP ? -1 : direction == DOWN ? 1 : direction == RIGHT ? 1 : direction == LEFT ? -1 : error("Direction ", direction, " not recognized") - -##### Callbacks ##### -function mousedown_cb(ptr::Ptr, eventp::Ptr, handler::MouseHandler{U}) where U - evt = unsafe_load(eventp) - handler.buttonpress[] = MouseButton{U}(handler.widget, evt) - Int32(false) -end -function mouseup_cb(ptr::Ptr, eventp::Ptr, handler::MouseHandler{U}) where U - evt = unsafe_load(eventp) - handler.buttonrelease[] = MouseButton{U}(handler.widget, evt) - Int32(false) -end -function mousemove_cb(ptr::Ptr, eventp::Ptr, handler::MouseHandler{U}) where U - evt = unsafe_load(eventp) - pos = XY{U}(handler.widget, evt) - # This doesn't support multi-button moves well, but those are rare in most GUIs and - # users can examine `modifiers` directly. - button = 0 - if evt.state & Gtk.GdkModifierType.BUTTON1 != 0 - button = 1 - elseif evt.state & Gtk.GdkModifierType.BUTTON2 != 0 - button = 2 - elseif evt.state & Gtk.GdkModifierType.BUTTON3 != 0 - button = 3 - end - handler.motion[] = MouseButton(pos, button, evt.event_type, evt.state) - Int32(false) -end -function mousescroll_cb(ptr::Ptr, eventp::Ptr, handler::MouseHandler{U}) where U - evt = unsafe_load(eventp) - handler.scroll[] = MouseScroll{U}(handler.widget, evt) - Int32(false) -end diff --git a/src/player.glade b/src/player.glade deleted file mode 100644 index 614767b..0000000 --- a/src/player.glade +++ /dev/null @@ -1,326 +0,0 @@ - - - - - - 1 - 100 - 1 - 1 - 10 - - - 1 - 100 - 1 - 1 - 10 - - - True - False - 0 - none - - - True - False - vertical - - - True - True - adjustment2 - False - 0 - 0 - -1 - False - - - False - True - 0 - - - - - True - False - - - True - True - True - image6 - half - 0.56999999284744263 - 0.51999998092651367 - True - - - False - True - 0 - - - - - True - True - True - image7 - True - - - False - True - 1 - - - - - True - True - True - image8 - True - - - False - True - 2 - - - - - True - True - - - False - True - 3 - - - - - True - True - True - image9 - True - - - False - True - 4 - - - - - True - True - True - image10 - 0.47999998927116394 - True - - - False - True - 5 - - - - - False - True - 1 - - - - - - - - - - True - False - gtk-go-back - - - True - False - gtk-go-forward - - - True - False - gtk-go-forward - - - True - False - gtk-media-next - - - True - False - gtk-media-previous - - - True - False - gtk-media-stop - - - 200 - 50 - True - False - 0 - in - - - True - False - vertical - - - True - True - adjustment1 - False - 0 - 0 - -1 - False - - - False - True - 0 - - - - - True - False - - - True - True - True - image1 - half - 0.56999999284744263 - 0.51999998092651367 - True - - - False - True - 0 - - - - - True - True - True - image4 - True - - - False - True - 1 - - - - - True - True - True - image5 - True - - - False - True - 2 - - - - - True - True - - - False - True - 3 - - - - - True - True - True - image3 - True - - - False - True - 4 - - - - - True - True - True - image2 - 0.47999998927116394 - True - - - False - True - 5 - - - - - False - True - 1 - - - - - - - True - False - gtk-go-back - - - True - False - gtk-media-previous - - - True - False - gtk-media-stop - - - True - False - gtk-media-next - - diff --git a/src/player.ui b/src/player.ui new file mode 100644 index 0000000..0e98e81 --- /dev/null +++ b/src/player.ui @@ -0,0 +1,116 @@ + + + + + + + True + True + + + vertical + 1 + + + 6 + + + + + start + + + start + go-previous + + + + + start + media-skip-backward + + + + + start + media-playback-stop + + + + + center + + + + + end + media-skip-forward + + + + + end + go-next + + + + + + + + + True + True + + + vertical + 1 + + + 6 + + + + + start + + + start + go-previous + + + + + start + media-skip-backward + + + + + start + media-playback-stop + + + + + center + + + + + end + media-skip-forward + + + + + end + go-next + + + + + + + + diff --git a/src/precompile.jl b/src/precompile.jl index 3e84304..beed81a 100644 --- a/src/precompile.jl +++ b/src/precompile.jl @@ -1,83 +1,42 @@ using PrecompileTools @setup_workload begin - function eventbutton(c, event_type, btn, x=DeviceUnit(0), y=DeviceUnit(0), state=0) - xd, yd = GtkObservables.convertunits(DeviceUnit, c, x, y) - Gtk.GdkEventButton(event_type, - Gtk.gdk_window(widget(c)), - Int8(0), - UInt32(0), - convert(Float64, xd), convert(Float64, yd), - convert(Ptr{Float64},C_NULL), - UInt32(state), - UInt32(btn), - C_NULL, - 0.0, 0.0) - end - function eventscroll(c, direction, x=DeviceUnit(0), y=DeviceUnit(0), state=0) - xd, yd = GtkObservables.convertunits(DeviceUnit, c, x, y) - Gtk.GdkEventScroll(Gtk.GdkEventType.SCROLL, - Gtk.gdk_window(widget(c)), - Int8(0), - UInt32(0), - convert(Float64, xd), convert(Float64, yd), - UInt32(state), - direction, - convert(Ptr{Float64},C_NULL), - 0.0, 0.0, - 0.0, 0.0) - end - function eventmotion(c, btn, x, y) - xd, yd = GtkObservables.convertunits(DeviceUnit, c, x, y) - Gtk.GdkEventMotion(Gtk.GdkEventType.MOTION_NOTIFY, - Gtk.gdk_window(widget(c)), - Int8(0), - UInt32(0), - convert(Float64, xd), convert(Float64, yd), - convert(Ptr{Float64},C_NULL), - UInt32(btn), - Int16(0), - C_NULL, - 0.0, 0.0) - end - ModType = Gtk.GConstants.GdkModifierType - mask(btn) = - btn == 1 ? ModType.GDK_BUTTON1_MASK : - btn == 2 ? ModType.GDK_BUTTON2_MASK : - btn == 3 ? ModType.GDK_BUTTON3_MASK : - error(btn, " not recognized") + buttoncontroller(c) = Gtk4.find_controller(widget(c), GtkGestureClick) + motioncontroller(c) = Gtk4.find_controller(widget(c), GtkEventControllerMotion) + scrollcontroller(c) = Gtk4.find_controller(widget(c), GtkEventControllerScroll) imgrand = rand(RGB{N0f8}, 100, 100) + Gtk4.GLib.start_main_loop() @compile_workload begin # slider sl = slider(1:3) sl[] = (1:5, 3) precompile(sl) - destroy(sl) + sl=nothing # checkbox cb = checkbox(true) cb[] = false precompile(cb) - destroy(cb) + cb=nothing # togglebutton tb = togglebutton(true) tb[] = false precompile(tb) - destroy(tb) + tb=nothing # button btn = button("Push me") btn[] = nothing precompile(btn) - destroy(btn) + btn=nothing # colorbutton cbtn = colorbutton(RGB(0, 0, 0)) cbtn[] = RGB(1, 0, 0) precompile(cbtn) - destroy(cbtn) + cbtn=nothing # textbox tb1, tb2 = textbox("Edit me"), textbox(3; range=1:5) @@ -85,18 +44,18 @@ using PrecompileTools tb2[] = 4 precompile(tb1) precompile(tb2) - destroy(tb1); destroy(tb2) + tb1=tb2=nothing # textarea ta = textarea("Lorem ipsum...") ta[] = "...muspi meroL" precompile(ta) - destroy(ta) + ta=nothing # dropdown dd = dropdown(["one", "two", "three"]) precompile(dd) - destroy(dd) + dd=nothing precompile(Tuple{typeof(dropdown),Tuple{Vararg{String, 100}}}) # time: 0.042523906 precompile(Tuple{typeof(dropdown),Vector{Pair{String, Function}}}) # time: 0.01169991 @@ -104,57 +63,57 @@ using PrecompileTools lbl = label("Info") lbl[] = "other info" precompile(lbl) - destroy(lbl) + lbl=nothing precompile(Tuple{typeof(show),IOBuffer,Label}) # time: 0.07879969 # spinbutton sb = spinbutton(1:3) sb[] = (1:4, 4) precompile(sb) - destroy(sb) + sb=nothing # cyclicspinbutton csb = cyclicspinbutton(1:3, Observable(true)) csb[] = 4 precompile(csb) - destroy(csb) + csb=nothing # progressbar pb = progressbar(1:3) pb[] = 2 precompile(pb) - destroy(pb) + pb=nothing pb = progressbar(1.0 .. 3.0) pb[] = 2.2 precompile(pb) - destroy(pb) + pb=nothing # player - p = player(1:3) - precompile(p) - p[] = 2 - destroy(p) + #p = player(1:3) + #precompile(p) + #p[] = 2 + #p=nothing # timewidget tw = timewidget(Dates.Time(1,1,1)) tw[] = Dates.Time(2,2,2) precompile(tw) - destroy(tw) + tw=nothing # datetimewidget dtw = datetimewidget(DateTime(1,1,1,1,1,1)) dtw[] = DateTime(2,2,2,2,2,2) - destroy(dtw) + dtw=nothing # canvas try # if we don't have a display, this might fail? for U in (UserUnit, DeviceUnit) - c = canvas(U, 100, 100) + c = canvas(U, 100, 100; init_back=true) fill!(c, RGB(0, 0, 0)) fill!(c, RGBA(1, 1, 1, 1)) lastevent = Ref("nothing") press = map(btn->lastevent[] = "press", c.mouse.buttonpress) - signal_emit(widget(c), "button-press-event", Bool, eventbutton(c, BUTTON_PRESS, 1)) + signal_emit(buttoncontroller(c), "pressed", Nothing, Cint(1), 0.0, 0.0) xsig, ysig = Observable(20), Observable(20) draw(c, xsig, ysig) do cnvs, x, y copy!(cnvs, imgrand) @@ -164,9 +123,9 @@ using PrecompileTools circle(ctx, x, y, 5) stroke(ctx) end - destroy(c) end - c = canvas(UserUnit) + modifier = Ref{Gtk4.ModifierType}(Gtk4.ModifierType_NONE) # needed to simulate modifier state + c = canvas(UserUnit, 100, 100; init_back=true, modifier_ref=modifier) zr = Observable(ZoomRegion((1:11, 1:20))) zoomrb = init_zoom_rubberband(c, zr) zooms = init_zoom_scroll(c, zr) @@ -176,28 +135,36 @@ using PrecompileTools set_coordinates(cnvs, zr[]) fill!(cnvs, colorant"blue") end - signal_emit(widget(c), "button-press-event", Bool, - eventbutton(c, BUTTON_PRESS, 1, UserUnit(5), UserUnit(3), CONTROL)) - signal_emit(widget(c), "motion-notify-event", Bool, - eventmotion(c, mask(1), UserUnit(10), UserUnit(4))) - signal_emit(widget(c), "button-release-event", Bool, - eventbutton(c, GtkObservables.BUTTON_RELEASE, 1, UserUnit(10), UserUnit(4))) - signal_emit(widget(c), "button-press-event", Bool, - eventbutton(c, BUTTON_PRESS, 1, UserUnit(6), UserUnit(3), 0)) - signal_emit(widget(c), "motion-notify-event", Bool, - eventmotion(c, mask(1), UserUnit(7), UserUnit(2))) - signal_emit(widget(c), "button-press-event", Bool, - eventbutton(c, DOUBLE_BUTTON_PRESS, 1, UserUnit(5), UserUnit(4.5), CONTROL)) - signal_emit(widget(c), "scroll-event", Bool, - eventscroll(c, UP, UserUnit(8), UserUnit(4), CONTROL)) - signal_emit(widget(c), "scroll-event", Bool, - eventscroll(c, RIGHT, UserUnit(8), UserUnit(4), 0)) - destroy(c) + modifier[]=CONTROL | Gtk4.ModifierType_BUTTON1_MASK + xd, yd = convertunits(DeviceUnit, c, UserUnit(5), UserUnit(3)) + signal_emit(buttoncontroller(c), "pressed", Nothing, Cint(1), xd.val, yd.val) + xd, yd = convertunits(DeviceUnit, c, UserUnit(10), UserUnit(4)) + signal_emit(motioncontroller(c), "motion", Nothing, xd.val, yd.val) + signal_emit(buttoncontroller(c), "released", Nothing, Cint(1), xd.val, yd.val) + modifier[]=Gtk4.ModifierType_NONE + signal_emit(buttoncontroller(c), "pressed", Nothing, + Cint(1), convert(Float64, UserUnit(6)), convert(Float64, UserUnit(3))) + signal_emit(motioncontroller(c), "motion", Nothing, + convert(Float64, UserUnit(7)), convert(Float64, UserUnit(2))) + modifier[]=CONTROL | Gtk4.ModifierType_BUTTON1_MASK + xd, yd = convertunits(DeviceUnit, c, UserUnit(5), UserUnit(3)) + signal_emit(buttoncontroller(c), "pressed", Nothing, Cint(2), xd.val, yd.val) + modifier[]=CONTROL + signal_emit(scrollcontroller(c), "scroll", Bool, + convert(Float64, UserUnit(8)), convert(Float64, UserUnit(4))) + modifier[]=Gtk4.ModifierType_NONE + signal_emit(scrollcontroller(c), "scroll", Bool, + convert(Float64, UserUnit(8)), convert(Float64, UserUnit(4))) + c=nothing catch + @warn("GtkObservables canvas precompile code failure") end end + Gtk4.GLib.stop_main_loop(true) end +empty!(_ref_dict) # bandaid until reffing is sorted out + GC.gc(true) # allow canvases to finalize sleep(1) # ensure all timers are closed GC.gc(true) # allow canvases to finalize diff --git a/src/rubberband.jl b/src/rubberband.jl index 64e28a7..7cde7ac 100644 --- a/src/rubberband.jl +++ b/src/rubberband.jl @@ -65,8 +65,8 @@ function init_zoom_rubberband(canvas::Canvas{U}, Dict{String,Any}("enabled"=>enabled, "active"=>active, "init"=>init, "drag"=>drag, "finish"=>finish) end -zrb_init_default(btn) = btn.button == 1 && btn.clicktype == BUTTON_PRESS && (btn.modifiers & 0x0f) == CONTROL -zrb_reset_default(btn) = btn.button == 1 && btn.clicktype == DOUBLE_BUTTON_PRESS && (btn.modifiers & 0x0f) == CONTROL +zrb_init_default(btn) = btn.button == 1 && btn.clicktype == BUTTON_PRESS && btn.n_press == 1 && (btn.modifiers & CONTROL == CONTROL) +zrb_reset_default(btn) = btn.button == 1 && btn.clicktype == BUTTON_PRESS && btn.n_press == 2 && (btn.modifiers & CONTROL == CONTROL) # For rubberband, we draw the selection region on the front canvas, and repair # by copying from the back. @@ -128,7 +128,7 @@ function rubberband_move(c::Canvas, rb::RubberBand, btn, ctxcopy) # Draw the new rubberband rb.pos2 = btn.position rb_draw(r, rb) - reveal(c, false) + reveal(c) nothing end @@ -139,7 +139,7 @@ function rubberband_stop(c::GtkObservables.Canvas, rb::RubberBand, btn, ctxcopy, r = getgc(c) rb_set(r, rb) rb_erase(r, rb, ctxcopy) - reveal(c, false) + reveal(c) pos = btn.position x, y = pos.x, pos.y x1, y1 = rb.pos1.x, rb.pos1.y diff --git a/src/time.glade b/src/time.glade index 5af3689..5b10487 100644 --- a/src/time.glade +++ b/src/time.glade @@ -1,78 +1,47 @@ - - + + - True - False - 0 - none - + 0 + - True - False + 0 - True - True + 0 + 1 vertical - - False - True - 6 - - True - False + 0 : - - False - True - 7 - - True - True + 0 + 1 vertical - - False - True - 8 - - True - False + 0 : - - False - True - 9 - - True - True + 0 + 1 vertical - - False - True - 10 - - + diff --git a/src/widgets.jl b/src/widgets.jl index d95b6c2..23607d6 100644 --- a/src/widgets.jl +++ b/src/widgets.jl @@ -69,8 +69,8 @@ end init_observable2widget(widget::GtkWidget, id, observable) = init_observable2widget(defaultgetter, defaultsetter!, widget, id, observable) -defaultgetter(widget) = Gtk.G_.value(widget) -defaultsetter!(widget,val) = Gtk.G_.value(widget, val) +defaultgetter(widget) = Gtk4.value(widget) +defaultsetter!(widget,val) = Gtk4.value(widget, val) """ ondestroy(widget::GtkWidget, preserved) @@ -103,14 +103,14 @@ struct Slider{T<:Number} <: InputWidget{T} obj end end -Slider(observable::Observable{T}, widget::GtkScaleLeaf, id, preserved) where {T} = +Slider(observable::Observable{T}, widget::GtkScale, id, preserved) where {T} = Slider{T}(observable, widget, id, preserved) medianidx(r) = (ax = axes(r)[1]; return (first(ax)+last(ax))÷2) # differs from median(r) in that it always returns an element of the range medianelement(r::AbstractRange) = r[medianidx(r)] -slider(observable::Observable, widget::GtkScaleLeaf, id, preserved = []) = +slider(observable::Observable, widget::GtkScale, id, preserved = []) = Slider(observable, widget, id, preserved) """ @@ -138,14 +138,17 @@ function slider(range::AbstractRange; if widget === nothing widget = GtkScale(lowercase(first(orientation)) == 'v', first(range), last(range), step(range)) - Gtk.G_.size_request(widget, 200, -1) + Gtk4.draw_value(widget,true) + Gtk4.size_request(widget, 200, -1) else - adj = Gtk.Adjustment(widget) - Gtk.G_.lower(adj, first(range)) - Gtk.G_.upper(adj, last(range)) - Gtk.G_.step_increment(adj, step(range)) + adj = Gtk4.GtkAdjustment(widget) + Gtk4.configure!(adj; lower = first(range), upper = last(range), step_increment = step(range)) + end + Gtk4.value(widget, value) + if lowercase(first(orientation)) == 'v' + Gtk4.orientation(Gtk4.GtkOrientable(widget), + Gtk4.Orientation_VERTICAL) end - Gtk.G_.value(widget, value) ## widget -> observable id = signal_connect(widget, "value_changed") do w @@ -168,11 +171,8 @@ end # Is calling this `setindex!` too much of a pun? function Base.setindex!(s::Slider, (range,value)::Tuple{AbstractRange, Any}) first(range) <= value <= last(range) || error("$value is not within the span of $range") - adj = Gtk.Adjustment(widget(s)) - Gtk.G_.lower(adj, first(range)) - Gtk.G_.upper(adj, last(range)) - Gtk.G_.step_increment(adj, step(range)) - Gtk.@sigatom Gtk.G_.value(widget(s), value) + adj = Gtk4.GtkAdjustment(widget(s)) + @idle_add Gtk4.configure!(adj; value = value, lower = first(range), upper = last(range), step_increment = step(range)) end Base.setindex!(s::Slider, range::AbstractRange) = setindex!(s, (range, s[])) @@ -191,7 +191,7 @@ struct Checkbox <: InputWidget{Bool} end end -checkbox(observable::Observable, widget::GtkCheckButtonLeaf, id, preserved=[]) = +checkbox(observable::Observable, widget::GtkCheckButton, id, preserved=[]) = Checkbox(observable, widget, id, preserved) """ @@ -212,14 +212,14 @@ function checkbox(value::Bool; widget=nothing, observable=nothing, label="", own if widget === nothing widget = GtkCheckButton(label) end - Gtk.G_.active(widget, value) + Gtk4.active(widget, value) - id = signal_connect(widget, "clicked") do w - observable[] = Gtk.G_.active(w) + id = signal_connect(widget, "toggled") do w + observable[] = Gtk4.active(w) end preserved = [] - push!(preserved, init_observable2widget(w->Gtk.G_.active(w), - (w,val)->Gtk.G_.active(w, val), + push!(preserved, init_observable2widget(w->Gtk4.active(w), + (w,val)->Gtk4.active(w, val), widget, id, observable)) if own ondestroy(widget, preserved) @@ -245,7 +245,7 @@ struct ToggleButton <: InputWidget{Bool} end end -togglebutton(observable::Observable, widget::GtkToggleButtonLeaf, id, preserved=[]) = +togglebutton(observable::Observable, widget::GtkToggleButton, id, preserved=[]) = ToggleButton(observable, widget, id, preserved) """ @@ -266,14 +266,14 @@ function togglebutton(value::Bool; widget=nothing, observable=nothing, label="", if widget === nothing widget = GtkToggleButton(label) end - Gtk.G_.active(widget, value) + Gtk4.active(widget, value) - id = signal_connect(widget, "clicked") do w - setindex!(observable, Gtk.G_.active(w)) + id = signal_connect(widget, "toggled") do w + setindex!(observable, Gtk4.active(w)) end preserved = [] - push!(preserved, init_observable2widget(w->Gtk.G_.active(w), - (w,val)->Gtk.G_.active(w, val), + push!(preserved, init_observable2widget(w->Gtk4.active(w), + (w,val)->Gtk4.active(w, val), widget, id, observable)) if own ondestroy(widget, preserved) @@ -288,7 +288,7 @@ togglebutton(; value=false, widget=nothing, observable=nothing, label="", own=no struct Button <: InputWidget{Nothing} observable::Observable{Nothing} - widget::Union{GtkButtonLeaf,GtkToolButtonLeaf} + widget::GtkButtonLeaf id::Culong function Button(observable::Observable{Nothing}, widget, id) @@ -298,7 +298,7 @@ struct Button <: InputWidget{Nothing} end end -button(observable::Observable, widget::Union{GtkButtonLeaf,GtkToolButtonLeaf}, id) = +button(observable::Observable, widget::GtkButton, id) = Button(observable, widget, id) """ @@ -349,11 +349,11 @@ struct ColorButton{C} <: InputWidget{Nothing} end end -colorbutton(observable::Observable{C}, widget::GtkColorButtonLeaf, id, preserved = []) where {T, C <: Color{T, 3}} = - ColorButton{C}(observable, widget, id, preserved) +colorbutton(observable::Observable{C}, widget::GtkColorButton, id, preserved = []) where {T, C <: Color{T, 3}} = +ColorButton{C}(observable, widget, id, preserved) -Base.convert(::Type{RGBA}, gcolor::Gtk.GdkRGBA) = RGBA(gcolor.r, gcolor.g, gcolor.b, gcolor.a) -Base.convert(::Type{Gtk.GdkRGBA}, color::Colorant) = Gtk.GdkRGBA(red(color), green(color), blue(color), alpha(color)) +Base.convert(::Type{RGBA}, gcolor::Gtk4.GdkRGBA) = RGBA(gcolor.r, gcolor.g, gcolor.b, gcolor.a) +Base.convert(::Type{Gtk4.GdkRGBA}, color::Colorant) = Gtk4.GdkRGBA(red(color), green(color), blue(color), alpha(color)) """ colorbutton(color; widget=nothing, observable=nothing) @@ -364,19 +364,19 @@ Create a push button with color `color`. Clicking opens the Gtk color picker. Op - the (Observables.jl) `observable` coupled to this button (by default, creates a new observable) """ function colorbutton(; - color::C = RGB(0, 0, 0), - widget=nothing, - observable=nothing, - own=nothing) where {C <: Color3} + color::C = RGB(0, 0, 0), + widget=nothing, + observable=nothing, + own=nothing) where {C <: Color3} obsin = observable observable, color = init_wobsval(observable, color) if own === nothing own = observable != obsin end - getcolor(w) = get_gtk_property(w, :rgba, Gtk.GdkRGBA) - setcolor!(w, val) = set_gtk_property!(w, :rgba, convert(Gtk.GdkRGBA, val)) + getcolor(w) = get_gtk_property(w, :rgba, Gtk4.GdkRGBA) + setcolor!(w, val) = set_gtk_property!(w, :rgba, convert(Gtk4.GdkRGBA, val)) if widget === nothing - widget = GtkColorButton(convert(Gtk.GdkRGBA, color)) + widget = GtkColorButton(convert(Gtk4.GdkRGBA, color)) else setcolor!(widget, color) end @@ -410,16 +410,15 @@ struct Textbox{T} <: InputWidget{T} obj end end -Textbox(observable::Observable{T}, widget::GtkEntryLeaf, id, preserved, range) where {T} = +Textbox(observable::Observable{T}, widget::GtkEntry, id, preserved, range) where {T} = Textbox{T}(observable, widget, id, preserved, range) -textbox(observable::Observable, widget::GtkButtonLeaf, id, preserved = []) = +textbox(observable::Observable, widget::GtkButton, id, preserved = []) = Textbox(observable, widget, id, preserved) """ textbox(value=""; widget=nothing, observable=nothing, range=nothing, gtksignal=:activate) textbox(T::Type; widget=nothing, observable=nothing, range=nothing, gtksignal=:activate) - Create a box for entering text. `value` is the starting value; if you don't want to provide an initial value, you can constrain the type with `T`. Optionally specify the allowed range (e.g., `-10:10`) @@ -450,10 +449,19 @@ function textbox(::Type{T}; widget = GtkEntry() end set_gtk_property!(widget, "text", value) - - id = signal_connect(widget, gtksignal) do w, _... - setindex!(observable, entrygetter(w, observable, range)) - return false + + if gtksignal == "focus-leave" + controller = GtkEventControllerFocus(widget) + id = signal_connect(controller, "leave") do c, _... + w=Gtk4.widget(c) + setindex!(observable, entrygetter(w, observable, range)) + return false + end + else + id = signal_connect(widget, gtksignal) do w, _... + setindex!(observable, entrygetter(w, observable, range)) + return false + end end preserved = [] @@ -636,7 +644,7 @@ function dropdown(; choices=nothing, allstrings = all(x->isa(x, AbstractString), choices) allstrings || all(x->isa(x, Pair), choices) || throw(ArgumentError("all elements must either be strings or pairs, got $choices")) str2int = Dict{String,Int}() - getactive(w) = (val = GAccessor.active_text(w); val == C_NULL ? nothing : Gtk.bytestring(val)) + getactive(w) = Gtk4.active_text(w) setactive!(w, val) = (i = val !== nothing ? str2int[val] : -1; set_gtk_property!(w, :active, i)) if length(choices) > 0 if value === nothing || (observable isa Observable{String} && value ∉ juststring.(choices)) @@ -713,7 +721,6 @@ juststring(p::Pair{String}) = p.first pairaction(str::AbstractString) = x->nothing pairaction(p::Pair{String,F}) where {F<:Function} = p.second - # """ # radiobuttons: see the help for `dropdown` # """ @@ -778,7 +785,6 @@ pairaction(p::Pair{String,F}) where {F<:Function} = p.second # valbest # end - ### Output Widgets ######################## Label ############################# @@ -815,13 +821,13 @@ function label(value; if widget === nothing widget = GtkLabel(value) else - set_gtk_property!(widget, "label", value) + Gtk4.label(widget, value) end preserved = [] if syncsig let widget=widget push!(preserved, on(observable; weak=true) do val - set_gtk_property!(widget, "label", val) + Gtk4.label(widget, val) end) end end @@ -913,10 +919,10 @@ struct SpinButton{T<:Number} <: InputWidget{T} obj end end -SpinButton(observable::Observable{T}, widget::GtkSpinButtonLeaf, id, preserved) where {T} = +SpinButton(observable::Observable{T}, widget::GtkSpinButton, id, preserved) where {T} = SpinButton{T}(observable, widget, id, preserved) -spinbutton(observable::Observable, widget::GtkSpinButtonLeaf, id, preserved = []) = +spinbutton(observable::Observable, widget::GtkSpinButton, id, preserved = []) = SpinButton(observable, widget, id, preserved) """ @@ -940,25 +946,23 @@ function spinbutton(range::AbstractRange{T}; if isa(obsin, Observable) Tel = eltype(obsin) end - observable, value = init_wobsval(Tel, observable, value; default=range.start) + observable, value = init_wobsval(Tel, observable, value; default=first(range)) if own === nothing own = observable != obsin end if widget === nothing widget = GtkSpinButton( first(range), last(range), step(range)) - Gtk.G_.size_request(widget, 200, -1) + Gtk4.size_request(widget, 200, -1) else - adj = Gtk.Adjustment(widget) - Gtk.G_.lower(adj, first(range)) - Gtk.G_.upper(adj, last(range)) - Gtk.G_.step_increment(adj, step(range)) + adj = Gtk4.GtkAdjustment(widget) + Gtk4.configure!(adj; lower=first(range), upper=last(range), step_increment=step(range)) end if lowercase(first(orientation)) == 'v' - Gtk.G_.orientation(Gtk.GtkOrientable(widget), - Gtk.GConstants.GtkOrientation.VERTICAL) + Gtk4.orientation(Gtk4.GtkOrientable(widget), + Gtk4.Orientation_VERTICAL) end - Gtk.G_.value(widget, value) + Gtk4.value(widget, value) ## widget -> observable id = signal_connect(widget, "value_changed") do w @@ -981,11 +985,8 @@ end # Is calling this `setindex!` too much of a pun? function Base.setindex!(s::SpinButton, (range,value)::Tuple{AbstractRange,Any}) first(range) <= value <= last(range) || error("$value is not within the span of $range") - adj = Gtk.Adjustment(widget(s)) - Gtk.G_.lower(adj, first(range)) - Gtk.G_.upper(adj, last(range)) - Gtk.G_.step_increment(adj, step(range)) - Gtk.G_.value(widget(s), value) + adj = Gtk4.GtkAdjustment(widget(s)) + Gtk4.configure!(adj; value = value, lower = first(range), upper = last(range), step_increment = step(range)) end Base.setindex!(s::SpinButton, range::AbstractRange) = setindex!(s, (range, s[])) @@ -1003,10 +1004,10 @@ struct CyclicSpinButton{T<:Number} <: InputWidget{T} obj end end -CyclicSpinButton(observable::Observable{T}, widget::GtkSpinButtonLeaf, id, preserved) where {T} = +CyclicSpinButton(observable::Observable{T}, widget::GtkSpinButton, id, preserved) where {T} = CyclicSpinButton{T}(observable, widget, id, preserved) -cyclicspinbutton(observable::Observable, widget::GtkSpinButtonLeaf, id, preserved = []) = +cyclicspinbutton(observable::Observable, widget::GtkSpinButton, id, preserved = []) = CyclicSpinButton(observable, widget, id, preserved) """ @@ -1030,24 +1031,22 @@ function cyclicspinbutton(range::AbstractRange{T}, carry_up::Observable{Bool}; syncsig=true, own=nothing) where T obsin = observable - observable, value = init_wobsval(T, observable, value; default=range.start) + observable, value = init_wobsval(T, observable, value; default=first(range)) if own === nothing own = observable != obsin end if widget === nothing widget = GtkSpinButton(first(range) - step(range), last(range) + step(range), step(range)) - Gtk.G_.size_request(widget, 200, -1) + Gtk4.size_request(widget, 200, -1) else - adj = Gtk.Adjustment(widget) - Gtk.G_.lower(adj, first(range) - step(range)) - Gtk.G_.upper(adj, last(range) + step(range)) - Gtk.G_.step_increment(adj, step(range)) + adj = Gtk4.GtkAdjustment(widget) + Gtk4.configure!(adj; lower = first(range) - step(range), upper = last(range) + step(range), step_increment = step(range)) end if lowercase(first(orientation)) == 'v' - Gtk.G_.orientation(Gtk.GtkOrientable(widget), - Gtk.GConstants.GtkOrientation.VERTICAL) + Gtk4.orientation(Gtk4.GtkOrientable(widget), + Gtk4.Orientation_VERTICAL) end - Gtk.G_.value(widget, value) + Gtk4.value(widget, value) ## widget -> observable id = signal_connect(widget, "value_changed") do w @@ -1093,7 +1092,7 @@ struct ProgressBar{T <: Number} <: Widget obj end end -ProgressBar(observable::Observable{T}, widget::GtkProgressBarLeaf, preserved) where {T} = +ProgressBar(observable::Observable{T}, widget::GtkProgressBar, preserved) where {T} = ProgressBar{T}(observable, widget, preserved) # convert a member of the interval into a decimal diff --git a/test/runtests.jl b/test/runtests.jl index ddf954d..b5d2b81 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -3,12 +3,12 @@ using ImageMagick end -using GtkObservables, Gtk.ShortNames, IntervalSets, Graphics, Colors, +using GtkObservables, Gtk4, IntervalSets, Graphics, Colors, TestImages, FileIO, FixedPointNumbers, RoundingIntegers, Dates, Cairo, IdentityRanges using Test -include("tools.jl") +Gtk4.GLib.start_main_loop() @testset "Widgets" begin ## label @@ -18,7 +18,7 @@ include("tools.jl") @test get_gtk_property(l, "label", String) == "Hello" l[] = "world" @test get_gtk_property(l, "label", String) == "world" - @test string(l) == string("Gtk.GtkLabelLeaf with ", string(observable(l))) + @test string(l) == string("GtkLabelLeaf with ", string(observable(l))) # Test other elements of the Observables API counter = Ref(0) ofunc = on(l) do _ @@ -35,55 +35,52 @@ include("tools.jl") end @test ldouble[] == "and againand again" # printing - @test string(l) == "Gtk.GtkLabelLeaf with Observable{String} with 2 listeners. Value:\n\"and again\"" || - string(l) == "Gtk.GtkLabelLeaf with Observable(\"and again\")" + @test string(l) == "GtkLabelLeaf with Observable(\"and again\")" ## checkbox - w = Window("Checkbox") + w = GtkWindow("Checkbox") check = checkbox(label="click me") push!(w, check) - Gtk.showall(w) @test check[] == false - @test Gtk.G_.active(check.widget) == false + @test Gtk4.active(check.widget) == false check[] = true @test check[] - @test Gtk.G_.active(check.widget) - destroy(w) + @test Gtk4.active(check.widget) + Gtk4.destroy(w) sleep(0.1) # work around https://github.com/JuliaGraphics/Gtk.jl/issues/391#issuecomment-1107840526 ## togglebutton - w = Window("Togglebutton") + w = GtkWindow("Togglebutton") tgl = togglebutton(label="click me") push!(w, tgl) - Gtk.showall(w) @test tgl[] == false - @test Gtk.G_.active(tgl.widget) == false + @test Gtk4.active(tgl.widget) == false tgl[] = true @test tgl[] - @test Gtk.G_.active(tgl.widget) - destroy(w) + @test Gtk4.active(tgl.widget) + Gtk4.destroy(w) sleep(0.1) ## colorbutton - w = Window("Colorbutton") + w = GtkWindow("Colorbutton") cb = colorbutton(color=RGB(1, 0, 1)) push!(w, cb) - Gtk.showall(w) @test cb[] == RGB(1, 0, 1) cb[] = RGB(0, 1, 0) @test cb[] == RGB(0, 1, 0) - destroy(w) + Gtk4.destroy(w) sleep(0.1) ## textbox (aka Entry) txt = textbox("Type something") num = textbox(5, range=1:10) - lost_focus = textbox("Type something"; gtksignal = "focus-out-event") - win = Window("Textboxes") |> (bx = Box(:h)) + lost_focus = textbox("Type something"; gtksignal = "focus-leave") + win = GtkWindow("Textboxes") + bx = GtkBox(:h) + win[] = bx push!(bx, txt) push!(bx, num) push!(bx, lost_focus) - Gtk.showall(win) @test get_gtk_property(txt, "text", String) == "Type something" txt[] = "ok" @test get_gtk_property(txt, "text", String) == "ok" @@ -106,20 +103,19 @@ include("tools.jl") grab_focus(widget(lost_focus)) set_gtk_property!(lost_focus, "text", "Something!") @test lost_focus[] == "Type something" - signal_emit(widget(lost_focus), "focus-out-event", Bool, Gtk.GdkEventAny(0, Ptr{Nothing}(), 0)) + grab_focus(widget(txt)) @test get_gtk_property(lost_focus, "text", String) == "Something!" @test lost_focus[] == "Something!" - destroy(win) + Gtk4.destroy(win) sleep(0.1) ## textarea (aka TextView) v = textarea("Type something longer") - win = Window(v) - Gtk.showall(win) + win = GtkWindow(v) @test v[] == "Type something longer" v[] = "ok" - @test get_gtk_property(Gtk.G_.buffer(v.widget), "text", String) == "ok" - destroy(win) + @test get_gtk_property(Gtk4.buffer(v.widget), "text", String) == "ok" + Gtk4.destroy(win) sleep(0.1) ## slider @@ -140,8 +136,6 @@ include("tools.jl") @test s2[] == 3 s2[] = 11 @test s[] == 11 - destroy(s2) - destroy(s) sleep(0.1) # Updating the limits of the slider @@ -149,18 +143,16 @@ include("tools.jl") sleep(0.01) # For the Gtk eventloop @test s[] == 8 s[] = 1:7, 5 - sleep(0.01) + sleep(0.05) @test s[] == 5 sleep(0.1) ## dropdown dd = dropdown(("Strawberry", "Vanilla", "Chocolate")) @test dd[] === "Strawberry" - destroy(dd.widget) dd = dropdown(()) @test dd[] === nothing - destroy(dd.widget) dd = dropdown(("Strawberry", "Vanilla", "Chocolate"), value = "Vanilla") @test dd[] === "Vanilla" @@ -176,7 +168,6 @@ include("tools.jl") dd[] = "Caramel" @test dd[] == "Caramel" @test get_gtk_property(dd, "active", Int) == 1 - destroy(dd.widget) r = Ref(0) dd = dropdown(["Five"=>x->x[]=5, @@ -192,13 +183,11 @@ include("tools.jl") @test r[] == 7 dd[] = "Five" @test r[] == 5 - destroy(dd.widget) # if the Observable is just of type String, don't support unselected state (compatibility with 1.0.0) dd = dropdown(("Strawberry", "Vanilla", "Chocolate"), observable = Observable("")) @test dd[] === "Strawberry" @test_throws ArgumentError empty!(dd) - destroy(dd.widget) sleep(0.1) ## spinbutton @@ -207,11 +196,9 @@ include("tools.jl") @test s[] == 1 s[] = 3 @test s[] == 3 - destroy(s) - s = spinbutton(0:59, orientation="vertical") - @test G_.orientation(Orientable(widget(s))) == Gtk.GConstants.GtkOrientation.VERTICAL - destroy(s) + s = spinbutton(0.0:2.0:59.0, orientation="vertical") + @test Gtk4.orientation(GtkOrientable(widget(s))) == Gtk4.Orientation_VERTICAL # Updating the limits of the spinbutton s = spinbutton(1:15) @@ -240,11 +227,9 @@ include("tools.jl") b[] = 4 @test a[] == 5 @test b[] == 1 - destroy(a) s = cyclicspinbutton(0:59, carry_up, orientation="vertical") - @test G_.orientation(Orientable(widget(s))) == Gtk.GConstants.GtkOrientation.VERTICAL - destroy(s) + @test Gtk4.orientation(GtkOrientable(widget(s))) == Gtk4.Orientation_VERTICAL sleep(0.1) # timewidget @@ -288,57 +273,45 @@ const counter = Ref(0) @testset "Button" begin ## button - w = Window("Widgets") + w = GtkWindow("Widgets") b = button("Click me") push!(w, b) action = map(b) do val counter[] += 1 end - Gtk.showall(w) cc = counter[] # map seems to fire it once, so record the "new" initial value - click(b::GtkObservables.Button) = ccall((:gtk_button_clicked,Gtk.libgtk),Cvoid,(Ptr{Gtk.GObject},),b.widget) + click(b::GtkObservables.Button) = signal_emit(widget(b),"clicked",Nothing) GC.gc(true) click(b) - if VERSION >= v"1.2.0" - @test counter[] == cc+1 - else - @test_broken counter[] == cc+1 - end - destroy(w) - - # Make sure we can also put a ToolButton in a Button - button(; widget=ToolButton("Save as...")) + @test counter[] == cc+1 + Gtk4.destroy(w) end -if Gtk.libgtk_version >= v"3.10" - # To support GtkBuilder, we need this as the minimum libgtk version - @testset "Compound widgets" begin - ## player widget - s = Observable(1) - p = player(s, 1:8) - win = Window("Compound", 400, 100) |> (g = Grid()) - g[1,1] = p - btn_fwd = p.widget.step_forward - @test s[] == 1 - btn_fwd[] = nothing - @test s[] == 2 - p.widget.play_forward[] = nothing - for i = 1:7 - sleep(0.1) - end - @test s[] == 8 - @test string(p) == "GtkObservables.PlayerWithTextbox with Observable{Int64} with 2 listeners. Value:\n8" || - string(p) == "GtkObservables.PlayerWithTextbox with Observable(8)" - destroy(p) - destroy(win) - - p = player(1:1000) - win = Window("Compound 2", 400, 100) - push!(win, frame(p)) - widget(p).direction[] = 1 - destroy(p) - destroy(win) # this should not generate a lot of output +@testset "Compound widgets" begin + ## player widget + s = Observable(1) + p = player(s, 1:8) + win = GtkWindow("Compound", 400, 100) + g = GtkGrid() + win[]=g + g[1,1] = frame(p) + btn_fwd = p.widget.step_forward + @test s[] == 1 + btn_fwd[] = nothing + @test s[] == 2 + p.widget.play_forward[] = nothing + for i = 1:7 + sleep(0.1) end + @test s[] == 8 + @test string(p) == "GtkObservables.PlayerWithTextbox with Observable(8)" + Gtk4.destroy(win) + + p = player(1:1000) + win = GtkWindow("Compound 2", 400, 100) + push!(win, frame(p)) + widget(p).direction[] = 1 + Gtk4.destroy(win) # this should not generate a lot of output end @testset "CairoUnits" begin @@ -377,33 +350,27 @@ end @test BoundingBox(XY(2..4, -15..15)) === BoundingBox(2, 4, -15, 15) c = canvas(208, 207) - win = Window(c) - Gtk.showall(win) - reveal(c, true) - sleep(0.3) - can_test_width = !(VERSION.minor < 3 && Sys.iswindows()) + win = GtkWindow(c) + reveal(c) + sleep(1.0) + can_test_width = !Sys.iswindows() can_test_width && @test Graphics.width(c) == 208 - @test Graphics.height(c) == 207 + can_test_width && @test Graphics.height(c) == 207 @test isa(c, GtkObservables.Canvas{DeviceUnit}) - destroy(win) + Gtk4.destroy(win) c = canvas(UserUnit, 208, 207) - win = Window(c) - Gtk.showall(win) - reveal(c, true) + win = GtkWindow(c) + reveal(c) sleep(1.0) @test isa(c, GtkObservables.Canvas{UserUnit}) @test string(c) == "GtkObservables.Canvas{UserUnit}()" corner_dev = (DeviceUnit(208), DeviceUnit(207)) - can_test_coords = (VERSION < v"1.3" || get(ENV, "CI", nothing) != "true" || !Sys.islinux()) && - can_test_width + can_test_coords = (get(ENV, "CI", nothing) != "true" || !Sys.islinux()) && can_test_width for (coords, corner_usr) in ((BoundingBox(0, 1, 0, 1), (UserUnit(1), UserUnit(1))), (ZoomRegion((5:10, 3:5)), (UserUnit(5), UserUnit(10))), ((-1:1, 101:110), (UserUnit(110), UserUnit(1)))) set_coordinates(c, coords) if can_test_coords - # FIXME: the new JLL-based version fails on Travis. - # Unfortunately this is difficult to debug because it doesn't replicate - # locally or on a local headless server. See #91. @test GtkObservables.convertunits(DeviceUnit, c, corner_dev...) == corner_dev @test GtkObservables.convertunits(DeviceUnit, c, corner_usr...) == corner_dev @test GtkObservables.convertunits(UserUnit, c, corner_dev...) == corner_usr @@ -411,23 +378,35 @@ end end end - destroy(win) + Gtk4.destroy(win) c = canvas() - f = Frame(c) - @test isa(f, Gtk.GtkFrameLeaf) - destroy(f) + f = GtkFrame() + f[] = c.widget + @test isa(f, Gtk4.GtkFrameLeaf) c = canvas() - f = AspectFrame(c, "Some title", 0.5, 0.5, 3.0) - @test isa(f, Gtk.GtkAspectFrameLeaf) + f = GtkAspectFrame(0.5, 0.5, 3.0, true) + f[] = c.widget + @test isa(f, Gtk4.GtkAspectFrameLeaf) @test get_gtk_property(f, "ratio", Float64) == 3.0 - destroy(f) +end + +function gesture_click(g) + isa(g,GtkGestureClick) && g.button == 0 +end + +function find_gesture_click(w::GtkWidget) + list = Gtk4.observe_controllers(w) + i=findfirst(gesture_click, list) + i!==nothing ? list[i] : nothing end @testset "Canvas events" begin - win = Window() |> (c = canvas(UserUnit)) - Gtk.showall(win) + win = GtkWindow() + c = canvas(UserUnit) + win[] = c.widget + show(win) sleep(0.2) lastevent = Ref("nothing") press = map(btn->lastevent[] = "press", c.mouse.buttonpress) @@ -437,56 +416,62 @@ end scroll = map(btn->lastevent[] = "scroll", c.mouse.scroll) lastevent[] = "nothing" @test lastevent[] == "nothing" - signal_emit(widget(c), "button-press-event", Bool, eventbutton(c, BUTTON_PRESS, 1)) - sleep(0.1) - # FIXME: would prefer that this works on all Julia versions - VERSION >= v"1.2.0" && @test lastevent[] == "press" - signal_emit(widget(c), "button-release-event", Bool, eventbutton(c, GtkObservables.BUTTON_RELEASE, 1)) + ec = find_gesture_click(widget(c)) + signal_emit(ec, "pressed", Nothing, Int32(1), 0.0, 0.0) sleep(0.1) + @test lastevent[] == "press" + signal_emit(ec, "released", Nothing, Int32(1), 0.0, 0.0) sleep(0.1) - VERSION >= v"1.2.0" && @test lastevent[] == "release" - signal_emit(widget(c), "scroll-event", Bool, eventscroll(c, UP)) sleep(0.1) + @test lastevent[] == "release" + ec = Gtk4.find_controller(widget(c), GtkEventControllerScroll) + signal_emit(ec, "scroll", Bool, 1.0, 0.0) sleep(0.1) - VERSION >= v"1.2.0" && @test lastevent[] == "scroll" - signal_emit(widget(c), "motion-notify-event", Bool, eventmotion(c, 0, UserUnit(20), UserUnit(15))) + @test lastevent[] == "scroll" + ec = Gtk4.find_controller(widget(c), GtkEventControllerMotion) + signal_emit(ec, "motion", Nothing, UserUnit(20).val, UserUnit(15).val) sleep(0.1) sleep(0.1) - VERSION >= v"1.2.0" && @test lastevent[] == "motion to UserUnit(20.0), UserUnit(15.0)" - destroy(win) + @test lastevent[] == "motion to UserUnit(20.0), UserUnit(15.0)" + Gtk4.destroy(win) end @testset "Popup" begin - popupmenu = Menu() - popupitem = MenuItem("Popup menu...") + popupmenu = Gtk4.GLib.GMenu() + popupitem = Gtk4.GLib.GMenuItem("Popup menu...") push!(popupmenu, popupitem) - Gtk.showall(popupmenu) - win = Window() |> (c = canvas()) + popover = GtkPopoverMenu(popupmenu) + win = GtkWindow() + modifier = Ref{Gtk4.ModifierType}(Gtk4.ModifierType_NONE) # needed to simulate modifier state + c = canvas(;modifier_ref = modifier) + Gtk4.parent(popover, widget(c)) + win[] = widget(c) popuptriggered = Ref(false) push!(c.preserved, map(c.mouse.buttonpress) do btn if btn.button == 3 && btn.clicktype == BUTTON_PRESS - popup(popupmenu, btn.gtkevent) # use the raw Gtk event + pos = Gtk4._GdkRectangle(round(Int32,btn.position.x.val),round(Int32,btn.position.y.val),1,1) + Gtk4.G_.set_pointing_to(popover, Ref(pos)) + Gtk4.popup(popover) popuptriggered[] = true nothing end end) yield() @test !popuptriggered[] - evt = eventbutton(c, BUTTON_PRESS, 1) - signal_emit(widget(c), "button-press-event", Bool, evt) + ec = find_gesture_click(widget(c)) + signal_emit(ec, "pressed", Nothing, Int32(1), 0.0, 0.0) yield() @test !popuptriggered[] - evt = eventbutton(c, BUTTON_PRESS, 3) - signal_emit(widget(c), "button-press-event", Bool, evt) - @test popuptriggered[] - destroy(win) - destroy(popupmenu) + modifier[] = Gtk4.ModifierType_BUTTON3_MASK + signal_emit(ec, "pressed", Nothing, Int32(1), 0.0, 0.0) + @test popuptriggered[] # this requires simulating a right click, which might require constructing a GdkEvent structure + Gtk4.destroy(win) end @testset "Drawing" begin img = testimage("lighthouse") c = canvas(UserUnit, size(img, 2), size(img, 1)) - win = Window(c) + win = GtkWindow(c) xsig, ysig = Observable(20), Observable(20) draw(c, xsig, ysig) do cnvs, x, y copy!(c, img) @@ -496,11 +481,10 @@ end circle(ctx, x, y, 5) stroke(ctx) end - Gtk.showall(win) xsig[] = 100 sleep(1) # Check that the displayed image is as expected - if get(ENV, "CI", nothing) != "true" || !Sys.islinux() || VERSION < v"1.3" # broken on Travis + if get(ENV, "CI", nothing) != "true" || !Sys.islinux() fn = joinpath(tempdir(), "circled.png") Cairo.write_to_png(getgc(c).surface, fn) imgout = load(fn) @@ -508,7 +492,7 @@ end @test imgout[25,100] == imgout[16,100] == imgout[20,105] == colorant"red" @test imgout[20,100] == img[20,100] end - destroy(win) + Gtk4.destroy(win) end # For testing ZoomRegion support for non-AbstractArray objects @@ -609,7 +593,9 @@ end @testset "More zoom/pan" begin ### Simulate the mouse clicks, etc. to trigger zoom/pan - win = Window() |> (c = canvas(UserUnit)) + modifier = Ref{Gtk4.ModifierType}(Gtk4.ModifierType_NONE) # needed to simulate modifier state + c = canvas(UserUnit;modifier_ref=modifier) + win = GtkWindow(c) zr = Observable(ZoomRegion((1:11, 1:20))) zoomrb = init_zoom_rubberband(c, zr) zooms = init_zoom_scroll(c, zr) @@ -619,58 +605,68 @@ end set_coordinates(c, zr[]) fill!(c, colorant"blue") end - Gtk.showall(win) sleep(0.1) # Zoom by rubber band - signal_emit(widget(c), "button-press-event", Bool, - eventbutton(c, BUTTON_PRESS, 1, UserUnit(5), UserUnit(3), CONTROL)) - signal_emit(widget(c), "motion-notify-event", Bool, - eventmotion(c, mask(1), UserUnit(10), UserUnit(4))) - signal_emit(widget(c), "button-release-event", Bool, - eventbutton(c, GtkObservables.BUTTON_RELEASE, 1, UserUnit(10), UserUnit(4))) + # need to simulate control modifier + button 1 + modifier[]=CONTROL | Gtk4.ModifierType_BUTTON1_MASK + ec = Gtk4.find_controller(widget(c), GtkGestureClick) + xd, yd = GtkObservables.convertunits(DeviceUnit, c, UserUnit(5), UserUnit(3)) + signal_emit(ec, "pressed", Nothing, Int32(1), xd.val, yd.val) + modifier[]=CONTROL | Gtk4.ModifierType_BUTTON1_MASK + ecm = Gtk4.find_controller(widget(c), GtkEventControllerMotion) + xd, yd = GtkObservables.convertunits(DeviceUnit, c, UserUnit(10), UserUnit(4)) + signal_emit(ecm, "motion", Nothing, xd.val, yd.val) + signal_emit(ec, "released", Nothing, Int32(1), xd.val, yd.val) + modifier[]=Gtk4.ModifierType_NONE @test zr[].currentview.x == 5..10 @test zr[].currentview.y == 3..4 # Ensure that the rubber band damage has been repaired - if get(ENV, "CI", nothing) != "true" || !Sys.islinux() || VERSION < v"1.3" # broken on Travis + if get(ENV, "CI", nothing) != "true" || !Sys.islinux() fn = tempname() Cairo.write_to_png(getgc(c).surface, fn) imgout = load(fn) rm(fn) @test all(x->x==colorant"blue", imgout) end - + # Pan-drag - signal_emit(widget(c), "button-press-event", Bool, - eventbutton(c, BUTTON_PRESS, 1, UserUnit(6), UserUnit(3), 0)) - signal_emit(widget(c), "motion-notify-event", Bool, - eventmotion(c, mask(1), UserUnit(7), UserUnit(2))) + modifier[]=Gtk4.ModifierType_BUTTON1_MASK + xd, yd = GtkObservables.convertunits(DeviceUnit, c, UserUnit(6), UserUnit(3)) + signal_emit(ec, "pressed", Nothing, Int32(1), xd.val, yd.val) + xd, yd = GtkObservables.convertunits(DeviceUnit, c, UserUnit(7), UserUnit(2)) + signal_emit(ecm, "motion", Nothing, xd.val, yd.val) + signal_emit(ec, "released", Nothing, Int32(1), xd.val, yd.val) + modifier[]=Gtk4.ModifierType_NONE @test zr[].currentview.x == 4..9 @test zr[].currentview.y == 4..5 - + # Reset - signal_emit(widget(c), "button-press-event", Bool, - eventbutton(c, DOUBLE_BUTTON_PRESS, 1, UserUnit(5), UserUnit(4.5), CONTROL)) + modifier[]=CONTROL | Gtk4.ModifierType_BUTTON1_MASK + signal_emit(ec, "pressed", Nothing, Int32(2), xd.val, yd.val) @test zr[].currentview.x == 1..20 @test zr[].currentview.y == 1..11 - + # Zoom-scroll - signal_emit(widget(c), "scroll-event", Bool, - eventscroll(c, UP, UserUnit(8), UserUnit(4), CONTROL)) + modifier[] = Gtk4.ModifierType_NONE + xd, yd = GtkObservables.convertunits(DeviceUnit, c, UserUnit(8), UserUnit(4)) + signal_emit(ecm, "motion", Nothing, xd.val, yd.val) + modifier[]=CONTROL + ecs = Gtk4.find_controller(widget(c), GtkEventControllerScroll) + signal_emit(ecs, "scroll", Bool, 0.0, 1.0) @test zr[].currentview.x == 4..14 - @test zr[].currentview.y == 2..8 - + @test zr[].currentview.y == 1..7 + # Pan-scroll - signal_emit(widget(c), "scroll-event", Bool, - eventscroll(c, RIGHT, UserUnit(8), UserUnit(4), 0)) + modifier[] = Gtk4.ModifierType_NONE + signal_emit(ecs, "scroll", Bool, 1.0, 0.0) @test zr[].currentview.x == 5..15 - @test zr[].currentview.y == 2..8 - - signal_emit(widget(c), "scroll-event", Bool, - eventscroll(c, DOWN, UserUnit(8), UserUnit(4), 0)) + @test zr[].currentview.y == 1..7 + + signal_emit(ecs, "scroll", Bool, 0.0, -1.0) @test zr[].currentview.x == 5..15 - @test zr[].currentview.y == 3..9 - + @test zr[].currentview.y == 2..8 + destroy(win) end @@ -682,13 +678,13 @@ end surf = GtkObservables.image_surface(fill(val, 3, 5)) @test surf.height == 3 && surf.width == 5 @test all(x->x == reinterpret(UInt32, cmp), surf.data) - destroy(surf) + Cairo.destroy(surf) end end @testset "Layout" begin - g = Grid() - g[1,1] = textbox("hello") + g = GtkGrid() + g[1,1] = textbox("hello").widget # probably violates the spirit of this test end examplepath = joinpath(dirname(dirname(@__FILE__)), "examples") diff --git a/test/tools.jl b/test/tools.jl deleted file mode 100644 index 7eff554..0000000 --- a/test/tools.jl +++ /dev/null @@ -1,47 +0,0 @@ -# Simulate user inputs -function eventbutton(c, event_type, btn, x=DeviceUnit(0), y=DeviceUnit(0), state=0) - xd, yd = GtkObservables.convertunits(DeviceUnit, c, x, y) - Gtk.GdkEventButton(event_type, - Gtk.gdk_window(widget(c)), - Int8(0), - UInt32(0), - convert(Float64, xd), convert(Float64, yd), - convert(Ptr{Float64},C_NULL), - UInt32(state), - UInt32(btn), - C_NULL, - 0.0, 0.0) -end -function eventscroll(c, direction, x=DeviceUnit(0), y=DeviceUnit(0), state=0) - xd, yd = GtkObservables.convertunits(DeviceUnit, c, x, y) - Gtk.GdkEventScroll(Gtk.GdkEventType.SCROLL, - Gtk.gdk_window(widget(c)), - Int8(0), - UInt32(0), - convert(Float64, xd), convert(Float64, yd), - UInt32(state), - direction, - convert(Ptr{Float64},C_NULL), - 0.0, 0.0, - 0.0, 0.0) -end -function eventmotion(c, btn, x, y) - xd, yd = GtkObservables.convertunits(DeviceUnit, c, x, y) - Gtk.GdkEventMotion(Gtk.GdkEventType.MOTION_NOTIFY, - Gtk.gdk_window(widget(c)), - Int8(0), - UInt32(0), - convert(Float64, xd), convert(Float64, yd), - convert(Ptr{Float64},C_NULL), - UInt32(btn), - Int16(0), - C_NULL, - 0.0, 0.0) -end - -const ModType = Gtk.GConstants.GdkModifierType -mask(btn) = - btn == 1 ? ModType.GDK_BUTTON1_MASK : - btn == 2 ? ModType.GDK_BUTTON2_MASK : - btn == 3 ? ModType.GDK_BUTTON3_MASK : - error(btn, " not recognized")