Skip to content

Commit

Permalink
Add documentation for atsign-guarded, mouse handler
Browse files Browse the repository at this point in the history
This also:
- adds some hints about properly specifying the types
in signal handler for the Event argument.
- extends atsign-guarded to work with do-block functions
  • Loading branch information
timholy committed Sep 10, 2015
1 parent 32aad6d commit ceb861c
Show file tree
Hide file tree
Showing 4 changed files with 88 additions and 10 deletions.
32 changes: 29 additions & 3 deletions README.md
Expand Up @@ -330,8 +330,8 @@ keep in mind that you can always address other variables from inside your functi
id = signal_connect((widget, event) -> cb_buttonpressed(widget, event, guistate, drawfunction, ...), b, "button-press-event")
```
Finally, in some situations you may want or need to use an [approach that is more analagous to julia's `cfunction` callback syntax](doc/more_signals.md).
One advantage of this alternative approach is that, in cases of error, the backtraces are much more informative.
In some situations you may want or need to use an [approach that is more analagous to julia's `cfunction` callback syntax](doc/more_signals.md). One advantage of this alternative approach is that, in cases of error, the backtraces are much more informative. See also https://github.com/JuliaLang/Gtk.jl/issues/161, which documents a bug currently affecting the "simple" approach shown here.
### Usage without the REPL
Expand Down Expand Up @@ -378,7 +378,7 @@ Generic drawing is done on a `Canvas`. You control what appears on this canvas b
using Gtk.ShortNames, Graphics
c = @Canvas()
win = @Window(c, "Canvas")
draw(c) do widget
@guarded draw(c) do widget
ctx = getgc(c)
h = height(c)
w = width(c)
Expand All @@ -399,6 +399,32 @@ This `draw` function will get called each time the window gets resized or otherw
See Julia's standard-library documentation for more information on graphics.
Errors in the `draw` function can corrupt Gtk's internal state; if
this happens, you have to quit julia and start a fresh session. To
avoid this problem, the `@guarded` macro wraps your code in a
`try/catch` block and prevents the corruption. It is especially useful
when initially writing and debugging code. See [further
discussion](doc/more_signals.md) about when `@guarded` is relevant.
Finally, `Canvas`es have a field called `mouse` that allows you to
easily write callbacks for mouse events:
```jl
c.mouse.button1press = @guarded (widget, event) -> begin
ctx = getgc(widget)
set_source_rgb(ctx, 0, 1, 0)
arc(ctx, event.x, event.y, 5, 0, 2pi)
stroke(ctx)
reveal(widget)
end
```
This will draw a green circle on the canvas at every mouse click.
Resizing the window will make them go away; they were drawn on the
canvas, but they weren't added to the `draw` function.
Note the use of the `@guarded` macro here, too.
#### Menus
In Gtk, the core element is the `MenuItem`.
Expand Down
40 changes: 36 additions & 4 deletions doc/more_signals.md
Expand Up @@ -4,15 +4,15 @@ In addition to the ["simple"
interface](../README.md#callbacks-and-signals), `signal_connect`
supports an approach that allows your callback function to be directly
compiled to machine code. Not only is this more efficient, but it can
occasionally be useful in avoiding problems from callback interrupts.
occasionally be useful in avoiding problems (see issue #161).

This alternative syntax is as follows:
```
signal_connect(cb, widget, signalname, return_type, parameter_type_tuple, after, user_data=widget)
```
where:

- `cb` is your callback function. You should use a generic function
- `cb` is your callback function. This will be compiled with `cfunction`, and you need to follow its rules. In particular, you should use a generic function
(i.e., one defined as `function foo(x,y,z) ... end`), and the
arguments and return type should match the GTK+ documentation for
the widget and signal ([see
Expand All @@ -37,10 +37,9 @@ where:
to operate. For example, you can pass other widgets, tuples of
values, etc. If omitted, it defaults to `widget`.

The callback's argument need to match the GTK documentation, with the
The callback's arguments need to match the GTK documentation, with the
exception of the `user_data` argument. (Rather than being a pointer,
`user_data` will automatically be converted back to an object.)
you.)

For example, consider a GUI in which pressing a button updates
a counter:
Expand Down Expand Up @@ -72,3 +71,36 @@ signal_connect(button_cb, button, "clicked", Void, (), false, (label, counter))

You should note that the value of `counter[]` matches the display in
the GUI.

#### Specifying the event type

If your callback function takes an `event` argument, it is important
to declare its type correctly. An easy way to do that is to first
write a callback using the "simple" interface, e.g.,

```jl
signal_connect(win, "delete-event") do widget, event
@show typeof(event)
@show event
end
```

and then use the reported type in `parameter_type_tuple`.

#### `@guarded`

The "simple" callback interface includes protections against
corrupting Gtk state from errors, but this `cfunction`-based approach
does not. Consequently, you may wish to use `@guarded` when writing
these functions. ([Canvas](../README.md#canvases) draw functions and
mouse event-handling are called through this interface, which is why
you should use `@guarded` there.) For functions that should return a
value, you can specify the value to be returned on error as the first
argument. For example:

```jl
const unhandled = convert(Int32, false)
@guarded unhandled function my_callback(widgetptr, ...)
...
end
```
16 changes: 14 additions & 2 deletions src/base.jl
Expand Up @@ -65,12 +65,24 @@ macro guarded(ex...)
length(ex) == 1 || error("@guarded requires 1 or 2 arguments")
ex = ex[1]
end
# do-block syntax
if ex.head == :call && length(ex.args) >= 2 && ex.args[2].head == :->
newbody = _guarded(ex.args[2], retval)
ret = deepcopy(ex)
ret.args[2] = Expr(ret.args[2].head, ret.args[2].args[1], newbody)
return esc(ret)
end
newbody = _guarded(ex, retval)
esc(Expr(ex.head, ex.args[1], newbody))
end

function _guarded(ex, retval)
isa(ex, Expr) && (
ex.head == :-> ||
(ex.head == :(=) && isa(ex.args[1],Expr) && ex.args[1].head == :call) ||
ex.head == :function
) || error("@guarded requires an expression defining a function")
newbody = quote
quote
begin
try
$(ex.args[2])
Expand All @@ -81,9 +93,9 @@ macro guarded(ex...)
end
end
end
esc(Expr(ex.head, ex.args[1], newbody))
end


@deprecate getindex(w::GtkContainer, child::GtkWidget, name::StringLike, T::Type) getproperty(w,name,child,T)
@deprecate setindex!(w::GtkContainer, value, child::GtkWidget, name::StringLike, T::Type) setproperty!(w,name,child,T,value)
@deprecate setindex!(w::GtkContainer, value, child::GtkWidget, name::StringLike) setproperty!(w,name,child,value)
Expand Down
10 changes: 9 additions & 1 deletion test/misc.jl
Expand Up @@ -21,7 +21,6 @@ end
print_with_color(:green, """
The following messages:
WARNING: Error in @guarded callback
ERROR: UndefVarError: k not defined
are expected and a sign of normal operation.
""")

Expand All @@ -33,4 +32,13 @@ are expected and a sign of normal operation.
@assert bar3(3,5) == nothing
@assert bar4(3,5) == unhandled

# Do-block syntax
c = @GtkCanvas()
win = @GtkWindow(c)
showall(win)
@guarded draw(c) do widget
error("oops")
end
destroy(win)

end

0 comments on commit ceb861c

Please sign in to comment.