[Note: while I was writing this post Ian's reply arrived and you should
probably read Ian's post before mine. He explained IMO very well how you
can solve your problem on a higher (i.e. application) level than my
description at the end of this post does. My attempt is to answer your
question about the interaction of event processing and GUI behavior on a
more technical (lower) level.]
On 11.01.2017 19:04 52midnight... wrote:
> I'm new to FLTK and GUI coding, but have a background in embedded code
> for microcontrollers. Typically, after setting things up, such code
> enters a MainLoop in which a set of flags are repeatedly tested, and
> subroutines executed if they're set. At the same time, hardware
> interrupts occur in the background, with CPU registers pushed and popped
> on the stack to ensure that processes don't clobber each other.
>
> I'm now facing a new situation in which these simple, clear concepts
> have been replaced by a welter of possibilities that I don't yet
> understand. There are awakes, handlers, checks, idles, timeouts and more
> that are somehow part of what I assume is a more complex version of my
> familiar MainLoop, but I've no idea how they interact.
What you described so far is basically how FLTK works as well. I'll try
to explain more of the details below.
> In addition,
> multiple "threads" are now possible, I assume because of the multicore
> CPUs now in use.
Threads, where mentioned in the FLTK docs, are meant to be what is
generally called threads in programming (aka multithreaded programming),
for instance pthreads, Windows native threads, C++11 threads to name
only a few.
There's one important restriction though in FLTK and likely in all GUI
toolkits that use threads: only the main thread can _safely_ access the
GUI (i.e. open/close windows, change widgets, draw things, etc.). If you
got to the point that you consider using threads you may want to read
the chapter "Advanced FLTK" which is mainly on using threads in FLTK.
http://www.fltk.org/doc-1.3/advanced.html
> I'm writing an FLTK GUI for a "PC Oscilloscope" using a microcontroller
> connected by a USB serial link. When I set a timeout to call "do_scan",
> it triggers a stream of serial ADC data that is used to rewrite the GUI
> oscilloscope screen. However, once the timeout is running, any click of
> a widget freezes the code, presumably because the widget callback
> clobbers the asynchronously timed blocking read of the serial port and
> subsequent screen redraw.
The most important rule with GUI programming (presumably with all GUI
tookits, but definitely with FLTK) is that callbacks must be very short.
Callbacks must never do blocking I/O or anything like this because they
are executed by the main FLTK thread that services all system events and
drawing. Otherwise the GUI may stall and appear unresponsive.
> Although the FLTK docn describes each of the above event loop
> components, it (necessarily) assumes an understanding of how the loop
> itself operates, something I don't have.
Thanks for the hint. I'll check this and try to enhance the docs if this
is really the case (it's very likely that this is indeed a missing piece).
> Can anyone recommend other docn or literature that I can use to clarify
> my understanding of this, and how to structure events so that
> independent asynchronous activities do not clobber each other?
I can't recommend any related docs except the FLTK documentation chapter
"Handling Events", but I assume you read this already.
http://www.fltk.org/doc-1.3/events.html
> Sincere thanks for any attempt to answer what I know is a complex question.
Okay, I'll try to explain at least the general behavior of the FLTK main
loop, first w/o using any threads. FLTK programs that don't do any
blocking calls will usually not need threads. All GUI and system event
processing and drawing is serialized by the FLTK event loop. A basic
FLTK program just executes Fl::run() and terminates after Fl::run()
returns. See test/hello.cxx and the chapter "FLTK Basics":
http://www.fltk.org/doc-1.3/basics.html
Fl::run() basically executes a loop like this (simplified):
while (Fl::first_window()) {
Fl::wait();
Fl::flush()
}
Fl::first_window() tests if any window is shown and thus the loop
terminates when all windows are closed.
Fl::wait() waits for system events (timers, mouse moves, clicks,
keyboard, whatever) and processes these events. Events are dispatched to
widgets by calling their handle() methods [2]. Events like button clicks
trigger the (per widget defined) callback [1] and thus enable the user's
code to be executed. As said before, this code must not block, or the
GUI event handling will stall.
Fl::wait() processes all system events like timers, mouse, keyboard and
window events like closing, resizing etc.. So if you start a timer with
Fl::add_timeout() etc. the timer callback must also be executed in a
short time (as Ian wrote, a few milliseconds). Fl::wait() returns when
there are no pending system events.
Fl::flush() draws all widgets that have changed or the entire window
after a resize() and such.
If you need threads there are ways to communicate with the main FLTK
thread, but you must take care of locking. Please read the above
mentioned chapter (Advanced FLTK) and feel free to ask if you need more
help.
One note to "idles". Idle callbacks are intended to be executed from the
main loop when the main loop is idle (no pending events). However, they
must (like all callbacks) return after a short time so they don't block
the event loop. They should rarely be necessary and are difficult to
implement. Since they must return shortly they have to know how long
they are running, stop and save their context somewhere, return to the
main loop, and be able to continue when they are called again.
HTH.
[1] callbacks are meant to make it easy to interact with a widget w/o
deriving one's own class. The functionality is limited and can be
fine-tuned by Fl_Widget::when(), but all widgets have at most one
callback. Buttons, for instance, have a callback for mouse clicks, input
widgets have a callback for keyboard events and/or focus changes.
[2] The handle() method can be overridden by deriving a class from a
FLTK widget. This is more complicated but also more powerful.