JuliaGizmos/GtkReactive.jl

Failed to push! errors with 'checkboxes'

tomerarnon opened this issue · 4 comments

This is an error I have so far only encountered with checkboxes when they are somehow associated with draw methods.
Upon pressing the checkbox for the first time, I receive this error message and all of the signals stop updating. i.e. sliders no longer update, other buttons no longer update their values, etc.

Failed to push!
    false
to node
    input-2: Signal{Bool}(false, nactions=3)

error at node: map(input-2)-2: Signal{Void}(nothing, nactions=0)
MethodError: Cannot `convert` an object of type Gtk.GtkCanvas to an object of type Void
This may have arisen from a call to the constructor Void(...),
since type constructors fall back to convert methods.
 in send_value!(::Reactive.Signal{Void}, ::Gtk.GtkCanvas, ::Int64) at C:\Users\Tomer\.julia\v0.5\Reactive\src\core.jl:154
 in (::Reactive.##25#26{##5#6{GtkReactive.Checkbox,GtkReactive.Canvas{GtkReactive.UserUnit},Reactive.Signal{Array{ColorTypes.RGB{FixedPointNumbers.Normed{UInt8,8}},2}},Reactive.Signal{GtkReactive.ZoomRegion{RoundingIntegers.RInt64}}},Tuple{Reactive.Signal{Bool}}})(::Reactive.Signal{Void}, ::Int64) at C:\Users\Tomer\.julia\v0.5\Reactive\src\operators.jl:41
 in do_action(::Reactive.Action, ::Int64) at C:\Users\Tomer\.julia\v0.5\Reactive\src\core.jl:162
 in run(::Int64) at C:\Users\Tomer\.julia\v0.5\Reactive\src\core.jl:244
 in (::Reactive.##17#19)() at .\task.jl:360

This code I am trying to run is basically:

imagesignal = map(slider, button) do s, b
     get_image(s, b)
end

show_image = map(button) do b
    if b
          display_image(canvas, imagesignal, zoom_region)
          setproperty... #visible
     else 
          setproperty... # not visible
    end
end

(i.e. only draw to the canvas if the button is pressed, but always check the slider value to update which image should be drawn.)

Where display_image creates a map(zoom_region, imagesignal) block that returns a signal of the zoomed image and a draw(canvas, imgsig) block that copy!()s the image into the canvas.

I can't say for sure whether I first ran into this before or after updating GtkReactive (it was yesterday), but I've since tried downgrading to older versions of GtkReactive/Reactive with no luck. Is the issue with the checkbox, with Reactive, or with neither?

Looks like a mapped function is sometimes returning a Bool and sometimes returning nothing (of type Void). Maybe check to make sure that show_image = map(button) do b ... always returns a consistent type? Or you could use map(button; typ=Any).

Unfortunately, I don't think that this is the problem. I have written a brief example to help illustrate what is going on in my code. It functions just like the problem discussed, i.e., one function selects an image based on a slider value, and another two functions deal with the drawing based on the other signals. Pushing the checkbox should make the image disappear and reappear. Instead, the image disappears the first time, and signals stop working subsequently.
One possibility is that the if clicked statement isn't the correct way of handling signals which should be idle?
In any case, as you can see, the function select_img always returns a consistent type.

using Gtk.ShortNames, GtkReactive, TestImages


function select_img(n ::Int)
    if n < 50
        return testimage("lighthouse");
    else n >= 50
        return testimage("mountainstream.png")
    end
end

function display_image(cnvs ::GtkReactive.Canvas{GtkReactive.UserUnit},
                        img ::Reactive.Signal{Array{ColorTypes.RGB{FixedPointNumbers.Normed{UInt8,8}},2}},
                        zoom_region ::Reactive.Signal{GtkReactive.ZoomRegion{RoundingIntegers.RInt64}},)
    imgsig = map(zoom_region, img) do r, i
        cv = r.currentview
        view(i, UnitRange{Int}(cv.y), UnitRange{Int}(cv.x))
    end
    redraw = draw(cnvs, imgsig) do c, image
        copy!(c, image)
        set_coords(c, value(zoom_region))
    end
end

function make_image_visible(btn ::GtkReactive.Checkbox,
                            cnvs ::GtkReactive.Canvas{GtkReactive.UserUnit},
                            img ::Reactive.Signal{Array{ColorTypes.RGB{FixedPointNumbers.Normed{UInt8,8}},2}},
                            zoom_region ::Reactive.Signal{GtkReactive.ZoomRegion{RoundingIntegers.RInt64}})
    show = map(btn) do clicked
            println(clicked);
        if clicked
            display_image(cnvs, img, zoom_region)
            setproperty!(widget(cnvs), :visible, true)
        else
            setproperty!(widget(cnvs), :visible, false)
        end
    end
end


###setup###
win = Window("Image");
c = canvas(UserUnit, 450,300);                                   
    setproperty!(widget(c), :expand, true)
g = Grid();
cb = checkbox(true, label="lighthouse");
s = slider(1:100)
box = Box(:v, visible=true,
          border_width = 10, spacing = 10);
push!(box, cb);
push!(box, s);
g[1,2] = widget(c)
g[1,1] = box
push!(win, g)
zr = Signal(ZoomRegion(Array{Any,2}(512,768)));             
zoomsigs1 = init_zoom_rubberband(c, zr);
########

image = map(signal(s)) do n
    select_img(n);
end

draw_c = make_image_visible(cb, c, image, zr)

showall(win)

There are a couple of issues here:

  • Remember that Reactive signals, if garbage-collected (GCed), stop functioning. The redraw signal of display_image gets dropped and therefore is vulnerable to GC.
  • You probably don't want to create new Signals every time the user clicks on the checkbox---the "graph" should largely be static in your GUI, and any changes are made by push!ing new values to "old" signals. You can create the signals once and make drawing conditional on the state of the checkbox via filterwhen.

Since I don't think this has anything specific to do with GtkReactive (it's more of a Reactive issue), I'm going to close this (but feel free to complain if you disagree or find other related problems).

BTW, if you don't know about it you might be interested in JuliaImages/ImageView.jl#115 (testers wanted 😉 )

Thanks for the reply; it was very helpful and enlightening. I hesitate to reopen this, but I want to make sure I get this right. The further along I progress the harder it will be to correct trivial but dire errors like this. I've rewritten the example, below. In this case I am returning the various signals so that they are recorded globally. Is this what you meant?
Also, thank you for the tip about ImageView.jl. I'll check it out!

function image_to_canvas(imgsig::Reactive.Signal{Array{ColorTypes.RGB{FixedPointNumbers.Normed{UInt8,8}},2}},
                         zr ::Reactive.Signal{GtkReactive.ZoomRegion{RoundingIntegers.RInt64}},
                         btn::GtkReactive.Checkbox,
                         c ::GtkReactive.Canvas)

    btnsig = map(btn) do b
        setproperty!(c, :visible, b)
    end

    imgzoom = map(zr, imgsig) do r, i
        cv = r.currentview
        view(i, UnitRange{Int}(cv.y), UnitRange{Int}(cv.x))
    end

    drawsig = draw(c, imgzoom) do canvas, i
        copy!(canvas, i)
        set_coords(c, value(zr))
    end
    drawsig = filterwhen(signal(btn), value(drawsig), drawsig);

    return btnsig, imgzoom, drawsig
end

function select_img(n ::Int)
    if n < 50
        return testimage("lighthouse");
    else n >= 50
        return testimage("mountainstream.png")
    end
end


win = Window("Image");
c = canvas(UserUnit, 450,300);
setproperty!(widget(c), :expand, false)
g = Grid();
cb = checkbox(true, label="lighthouse");
s = slider(1:100)
box = Box(:v, visible=true,
border_width = 10, spacing = 10);
push!(box, cb);
push!(box, s);
g[1,2] = widget(c)
g[1,1] = box
push!(win, g)
zr = Signal(ZoomRegion(Array{Any,2}(512,768)));
zoomsigs = init_zoom_rubberband(c, zr);

image = map(signal(s)) do n
    select_img(n);
end

imgsig = filterwhen(signal(cb), image, image)

btnsig, imgzoom, drawsig = image_to_canvas(imgsig, zr, cb, c)

showall(win)

I greatly appreciate the help, even though I know this isn't really an issue with GtkReactive.jl but entirely "user error". I'm surprised any of this ever worked at all, honestly, but I guess that's a common problem when you're programming as a rookie 😜.